package nxm.sys.libg;

/** A class to draw Mercator views.
    <pre>
      Transformation matrix:
        merc = RAD2DEG*Math.log(Math.tan(PI/4+lat*DEG2RAD/2));
        lat  = (Math.atan(Math.exp(DEG2RAD*merc)) - PI/4)*rad2deg*2;
    </pre>
    @author  Jeff Schoen
 */
public class ViewMercader extends View {

  private double[] tl2m,dtl2m;
  private static final int ndeg=400;

  /** Construct a new ViewMercader
      @param MP the MPlot associated with this ViewMercader
   */
  public ViewMercader (MPlot MP) {
    super(MP);
    prepTables();
    setup();
  }

  /** Scales latitude value to match Mercator projection.
   *  @param lat latitude value
   *  @return Mercator projection
   */
  public static double lat2mer (double lat) {
    if (lat>=85) return lat+94;
    if (lat<=-85) return lat-94;
    return RAD2DEG*Math.log(Math.tan(PI/4+lat*DEG2RAD/2));
  }

  /** Inverse of {@link #lat2mer}.
   *  @param mer Mercator projection
   *  @return latitude value
   */
  public static double mer2lat (double mer) {
    if (mer>=179) return mer-94;
    if (mer<=-179) return mer+94;
    return (Math.atan(Math.exp(DEG2RAD*mer)) - PI/4)*RAD2DEG*2;
  }

  /** Fill in the sincos lookup tables */
  private void prepTables () {
    tl2m = new double[ndeg+2];
    dtl2m = new double[ndeg+1];
    for (int i=0; i<201; i++) {
      double z = lat2mer((double)i);
      tl2m[i+200] = z;
      tl2m[200-i] = -z;
    }
    for (int i=0; i<=ndeg; i++) {
      dtl2m[i] = tl2m[i+1]-tl2m[i];
    }
  }

  @Override
  public int rwc2pix (double[] rwc, MPoint pix) {
    while (rwc[1]>+180) rwc[1] -= 360;
    while (rwc[1]<-180) rwc[1] += 360;
    int vis = rwc2pix (rwc, 0, 3, 1, MP.tpix);
    pix.x = MP.tpix.x[0]; pix.y = MP.tpix.y[0];
    return vis;
  }

  @Override
  public int pix2rwc (MPoint pix, double[] rwc) {
    double retx,rety,alat,plat; int ilat;
    double mxx = MP.mxx, mxb = MP.mxb;
    double myy = (double)(MP.iy1-MP.iy2) / (t_lat2mer(MP.ry2) - t_lat2mer(MP.ry1));
    double myb = MP.iy2 - t_lat2mer(MP.ry1)*myy;
    retx = (((double)pix.x-mxb)/mxx);
    rety = (((double)pix.y-myb)/myy);
    rwc[0] = retx;
    rwc[1] = mer2lat(rety);
    rwc[2] = 0;
    return 1;
  }

  /** Fast conversion from pixel measure to RWC value on the Y axis used by {@link MPlot}.
   *  @param iy pixel measurement for y
   *  @return RWC value for y
   */
  public double iy2ry (int iy) {
    double myy = (double)(MP.iy1-MP.iy2) / (lat2mer(MP.ry2) - lat2mer(MP.ry1));
    double myb = MP.iy2 - lat2mer(MP.ry1)*myy;
    return mer2lat(((double)iy-myb)/myy);
  }

  /** Fast conversion from RWC value to pixel measure on the Y axis used by {@link MPlot}.
   *  @param ry RWC for y
   *  @return pixel measure for y
   */
  public int ry2iy (double ry) {
    double myy = (double)(MP.iy1-MP.iy2) / (lat2mer(MP.ry2) - lat2mer(MP.ry1));
    double myb = MP.iy2 - lat2mer(MP.ry1)*myy;
    return (int)(lat2mer(ry)*myy + myb);
  }

  // t_lat2mer(double) was deprecated in NeXtMidas 2.5.3 and made private in NeXtMidas 3.3.2
  private double t_lat2mer (double lat) {
    double alat,plat; int ilat;
    if (lat>=85.0) return lat+95;
    if (lat<=-85.0) return lat-95;
    alat = lat + 200.0;
    ilat = (int)alat;
    plat = alat-(double)ilat;
    return tl2m[ilat] + plat*dtl2m[ilat];
  }

  @Override
  public int rwc2pix (float[] buf, int off, int dim, int npts, Pix pix,
                      double xstart, double xdelta) {
    // THIS METHOD IS IDENTICAL TO THE ONE THAT USES A float[] BUFFER FOR INPUT,
    // EXCEPT FOR THE USE OF "float"
    int i,j=off,n=pix.setSize(npts),xoff=off/dim;
    float[] buf2 = new float[n*2];
    // Since 4.0.0 switched to i<n to minimize calculations in the loop, but switched to bufOffset inside the loop to
    // keep the same number of buffer data points and loop iterations
    int bufOffset = 0;
    for (i=0; i<n; j+=dim,i++) {
      double x = xstart + (xoff+i)*xdelta; // Since 4.0.0 NXM-3624 modified x-data calculation to avoid precision loss
      buf2[bufOffset++] = (float)Math.IEEEremainder(x,360.0);
      buf2[bufOffset++] = (float)Math.IEEEremainder(buf[j],360.0);
    }
    return rwc2pix(buf2,0,2,n,pix);
  }

  @Override
  public int rwc2pix (double[] buf, int off, int dim, int npts, Pix pix,
                      double xstart, double xdelta) {
    // THIS METHOD IS IDENTICAL TO THE ONE THAT USES A float[] BUFFER FOR INPUT,
    // EXCEPT FOR THE USE OF "double"
    int i,j=off,n=pix.setSize(npts),xoff=off/dim;
    double[] buf2 = new double[n*2];
    // Since 4.0.0 switched to i<n to minimize calculations in the loop, but switched to bufOffset inside the loop to
    // keep the same number of buffer data points and loop iterations
    int bufOffset = 0;
    for (i=0; i<n; j+=dim,i++) {
      double x = xstart + (xoff+i)*xdelta; // Since 4.0.0 NXM-3624 modified x-data calculation to avoid precision loss
      buf2[bufOffset++] = Math.IEEEremainder(x,360.0);
      buf2[bufOffset++] = Math.IEEEremainder(buf[j],360.0);
    }
    return rwc2pix(buf2,0,2,n,pix);
  }

  @Override
  public int rwc2pix (float[] buf, int off, int dim, int npts, Pix pix) {
    // THIS METHOD IS IDENTICAL TO THE ONE THAT USES A double[] BUFFER FOR INPUT
    if ((pix.flags&Pix.CLIP)!=0) return rwc2pixClip(buf,off,dim,npts,pix,true); // Special handling for CLIP
    int i,j=off,n=pix.setSize(npts); int[] pax=pix.x, pay=pix.y;
    double mxx = MP.mxx, mxb = MP.mxb;
    double myy = (double)(MP.iy1-MP.iy2) / (lat2mer(MP.ry2) - lat2mer(MP.ry1));
    double myb = MP.iy2 - lat2mer(MP.ry1)*myy;
    if (dim==2) {
      for (i=0; i<n; i++) {
        pax[i] = (int)(buf[j++]*mxx + mxb);
        pay[i] = (int)(lat2mer(buf[j++])*myy + myb);
      }
    } else {
      for (i=0; i<n; i++,j+=dim) {
        pax[i] = (int)(buf[j+0]*mxx + mxb);
        pay[i] = (int)(lat2mer(buf[j+1])*myy + myb);
      }
    }
    return n;
  }

  @Override
  public int rwc2pix (double[] buf, int off, int dim, int npts, Pix pix) {
    // THIS METHOD IS IDENTICAL TO THE ONE THAT USES A float[] BUFFER FOR INPUT
    if ((pix.flags&Pix.CLIP)!=0) return rwc2pixClip(buf,off,dim,npts,pix,true); // Special handling for CLIP
    int i,j=off,n=pix.setSize(npts); int[] pax=pix.x, pay=pix.y;
    double mxx = MP.mxx, mxb = MP.mxb;
    double myy = (double)(MP.iy1-MP.iy2) / (lat2mer(MP.ry2) - lat2mer(MP.ry1));
    double myb = MP.iy2 - lat2mer(MP.ry1)*myy;
    if (dim==2) {
      for (i=0; i<n; i++) {
        pax[i] = (int)(buf[j++]*mxx + mxb);
        pay[i] = (int)(lat2mer(buf[j++])*myy + myb);
      }
    } else {
      for (i=0; i<n; i++,j+=dim) {
        pax[i] = (int)(buf[j+0]*mxx + mxb);
        pay[i] = (int)(lat2mer(buf[j+1])*myy + myb);
      }
    }
    return n;
  }

  @Override
  int rwc2pix (double[] buf, int off, int dim, int npts, Pix pix, boolean isFill) {
    // Use the fill code since 3.9.2
    if (isFill && (pix.flags&Pix.CLIP)!=0) {
      return rwc2pixClipAdjustForFill(buf, off, dim, npts, pix, true);
    }
    // use the original code as there is no fill necessary
    return rwc2pix (buf, off, dim, npts, pix);
  }

  @Override
  public int rwc2dpix (double[] buf, int off, int dim, int npts, DPix pix) {
    // This method is identical to rwc2pix except that it will use double for everything, no int
    if ((pix.flags&Pix.CLIP)!=0) return rwc2dpixClip(buf,off,dim,npts,pix,true); // Special handling for CLIP
    int i,j=off,n=pix.setSize(npts); double[] pax=pix.x, pay=pix.y;
    double mxx = MP.mxx, mxb = MP.mxb;
    double myy = (double)(MP.iy1-MP.iy2) / (lat2mer(MP.ry2) - lat2mer(MP.ry1));
    double myb = MP.iy2 - lat2mer(MP.ry1)*myy;
    if (dim==2) {
      for (i=0; i<n; i++) {
        pax[i] = (buf[j++]*mxx + mxb);
        pay[i] = (lat2mer(buf[j++])*myy + myb);
      }
    } else {
      for (i=0; i<n; i++,j+=dim) {
        pax[i] = (buf[j+0]*mxx + mxb);
        pay[i] = (lat2mer(buf[j+1])*myy + myb);
      }
    }
    return n;
  }
}
