package nxm.sys.libg;

import nxm.sys.lib.Midas;
import nxm.sys.lib.TMatrix;

/** A class to draw 3D views.
    For this class, the RWC are just CARTESIAN XYZ data
    <pre>
      Transformation matrix:
        x' = mxx*x + mxb    x = (x'-mxb)/mxx
    </pre>
    @author Jeff Schoen
 */
public class View3D extends View {

  /** azimuth */
  public double azim = 0;

  /** elevation */
  public double elev = 0;

  /** roll */
  public double roll = 0;

  /** p distance */
  public double pdis=2.5;

  double xscale,xcenter,yscale,ycenter,zscale,zcenter;
  private TMatrix tm = new TMatrix();
  private TMatrix tmi = new TMatrix();

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

  @Override
  public boolean setObserver (double[] arg) {
    if (arg!=null) {
      tm.identity();
      if (arg.length>3 && arg[3]!=0) pdis=arg[3];
      if (arg.length>2) {tm.rotate(arg[2],TMatrix.ROLL); roll=arg[2];} // NXM-3410
      if (arg.length>1) {tm.rotate(arg[1],TMatrix.ELEV); elev=arg[1];}
      if (arg.length>0) {tm.rotate(arg[0],TMatrix.AZIM); azim=arg[0];}
    }
    setup();
    return true;
  }

  @Override
  public boolean getObserver (double[] arg) {
    arg[0] = azim;
    arg[1] = elev;
    arg[2] = roll;
    if (arg.length>3) arg[3] = pdis;
    return true;
  }

  @Override
  public boolean rotate (double[] arg) {
    tm.rotate(arg[0],TMatrix.AZIM);
    tm.rotate(arg[1],TMatrix.ELEV);
    tm.rotate(arg[2],TMatrix.ROLL);
    azim = Math.asin(tm.a21)*RAD2DEG; // NXM-3410
    elev = Math.asin(tm.a31)*RAD2DEG; // NXM-3410
    roll = Math.asin(tm.a32)*RAD2DEG; // NXM-3410
    return true;
  }

  @Override
  public void setup () {
    tm.invert(tmi);
    xscale = yscale = zscale = 1.0;
    if (MP.orx2!=MP.orx1) xscale = 2.0/(MP.orx2-MP.orx1); xcenter = (MP.orx1+MP.orx2)/2;
    if (MP.ory2!=MP.ory1) yscale = 2.0/(MP.ory2-MP.ory1); ycenter = (MP.ory1+MP.ory2)/2;
    if (MP.orz2!=MP.orz1) zscale = 2.0/(MP.orz2-MP.orz1); zcenter = (MP.orz1+MP.orz2)/2;
  }

  @Override
  public int rwc2pix (double[] rwc, MPoint pix) {
    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) {
    return 1;
  }

  @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)x;
      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++] = x;
      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) {
    int i,j=off,n=pix.setSize(npts);
    int[] pax=pix.x, pay=pix.y; float[] paz=pix.z;
    float mxx=(float)MP.mxx, myy=(float)MP.myy, mxb=(float)MP.mxb, myb=(float)MP.myb;
    float a11=(float)tmi.a11, a21=(float)tmi.a21, a31=(float)tmi.a31;
    float a12=(float)tmi.a12, a22=(float)tmi.a22, a32=(float)tmi.a32;
    float a13=(float)tmi.a13, a23=(float)tmi.a23, a33=(float)tmi.a33;
    float xs=(float)xscale, xc=(float)xcenter, ys=(float)yscale,
          yc=(float)ycenter, zs=(float)zscale, zc=(float)zcenter;
    float zobs=(float)pdis, x,y,z,xx,yy,zz,ff;
    if ((pix.flags&Pix.NORM)!=0) { xc=yc=zc=0; xs=ys=zs=1; }
    if (dim==2) for (i=0; i<n; i++) {
      x = (buf[j++]-xc)*xs;
      y = (buf[j++]-yc)*ys;
      xx = x*a11 + y*a21;
      yy = x*a12 + y*a22;
      zz = x*a13 + y*a23;
      ff = zobs/(zobs-zz);
      pax[i] = (int)(xx*ff*mxx + mxb);
      pay[i] = (int)(yy*ff*myy + myb);
      paz[i] = zz;
    }
    else for (i=0; i<n; i++) {
      x = (buf[j++]-xc)*xs;
      y = (buf[j++]-yc)*ys;
      z = (buf[j++]-zc)*zs;
      xx = x*a11 + y*a21 + z*a31;
      yy = x*a12 + y*a22 + z*a32;
      zz = x*a13 + y*a23 + z*a33;
      ff = zobs/(zobs-zz);
      pax[i] = (int)(xx*ff*mxx + mxb);
      pay[i] = (int)(yy*ff*myy + myb);
      paz[i] = zz;
    }
    pix.n = i;
    return n;
  }

  @Override
  public int rwc2pix (double[] buf, int off, int dim, int npts, Pix pix) {
    // If incoming data is invalid, print Midas warning
    if ((off + dim*npts) > buf.length) {
      Midas.log("Data is incompatible with View 3D", Midas.WARNING);
      return 0;
    }
    int i,j=off,n=pix.setSize(npts);
    int[] pax=pix.x, pay=pix.y; float[] paz=pix.z;
    double mxx=MP.mxx, myy=MP.myy, mxb=MP.mxb, myb=MP.myb;
    double a11=tmi.a11, a21=tmi.a21, a31=tmi.a31;
    double a12=tmi.a12, a22=tmi.a22, a32=tmi.a32;
    double a13=tmi.a13, a23=tmi.a23, a33=tmi.a33;
    double xs=xscale, xc=xcenter, ys=yscale, yc=ycenter, zs=zscale, zc=zcenter;
    double zobs=pdis, x,y,z,xx,yy,zz,ff;
    if ((pix.flags&Pix.NORM)!=0) { xc=yc=zc=0; xs=ys=zs=1; }
    if (dim==2) for (i=0; i<n; i++) {
      x = (buf[j++]-xc)*xs;
      y = (buf[j++]-yc)*ys;
      xx = x*a11 + y*a21;
      yy = x*a12 + y*a22;
      zz = x*a13 + y*a23;
      ff = zobs/(zobs-zz);
      pax[i] = (int)(xx*ff*mxx + mxb);
      pay[i] = (int)(yy*ff*myy + myb);
      paz[i] = (float)zz;
    }
    else for (i=0; i<n; i++) {
      x = (buf[j++]-xc)*xs;
      y = (buf[j++]-yc)*ys;
      z = (buf[j++]-zc)*zs;
      xx = x*a11 + y*a21 + z*a31;
      yy = x*a12 + y*a22 + z*a32;
      zz = x*a13 + y*a23 + z*a33;
      ff = zobs/(zobs-zz);
      pax[i] = (int)(xx*ff*mxx + mxb);
      pay[i] = (int)(yy*ff*myy + myb);
      paz[i] = (float)zz;
    }
    pix.n = i;
    return n;
  }

  /** get x scale
   *  @return x scale
   */
  public double getScale() { return xscale; }

  /** Checks if XY grid needs to be drawn in alternate location
   *  when in 3D GRAPH view
   *
   *  @return true if needs to be in alternate locations
   */
  boolean needAltXYGrid () {
    return Math.toDegrees(Math.asin(tm.a33)) < 0;
  }

  /** Checks if YZ grid needs to be drawn in alternate location
   *  when in 3D GRAPH view
   *
   *  @return true if needs to be in alternate locations
   */
  boolean needAltYZGrid () {
    return (Math.toDegrees(Math.asin(tm.a22)) < 0) && (elev < 0);
  }

  /** Checks if XZ grid needs to be drawn in alternate location
   *  when in 3D GRAPH view
   *
   *  @return true if needs to be in alternate locations
   */
  boolean needAltXZGrid () {
    return (roll < 0);
  }

  //************************************************************************************************
  //***************new 3.9.1 high precision double pixel methods ***********************************

  // Assumes 1 3D point (overridden in 4.1.3)
  @Override
  int rwc2dpix (double[] buf, DPix pix) {
    return rwc2dpix(buf,0,3,1,pix);
  }

  @Override
  public int rwc2dpix (double[] buf, int off, int dim, int npts, DPix pix) {
    int i,j=off,n=pix.setSize(npts);
    double[] pax=pix.x, pay=pix.y; double[] paz=pix.z;
    double mxx=MP.mxx, myy=MP.myy, mxb=MP.mxb, myb=MP.myb;
    double a11=tmi.a11, a21=tmi.a21, a31=tmi.a31;
    double a12=tmi.a12, a22=tmi.a22, a32=tmi.a32;
    double a13=tmi.a13, a23=tmi.a23, a33=tmi.a33;
    double xs=xscale, xc=xcenter, ys=yscale, yc=ycenter, zs=zscale, zc=zcenter;
    double zobs=pdis, x,y,z,xx,yy,zz,ff;
    if ((pix.flags&Pix.NORM)!=0) { xc=yc=zc=0; xs=ys=zs=1; }
    if (dim==2) for (i=0; i<n; i++) {
      x = (buf[j++]-xc)*xs;
      y = (buf[j++]-yc)*ys;
      xx = x*a11 + y*a21;
      yy = x*a12 + y*a22;
      zz = x*a13 + y*a23;
      ff = zobs/(zobs-zz);
      pax[i] = xx*ff*mxx + mxb;
      pay[i] = yy*ff*myy + myb;
      paz[i] = zz;
    }
    else for (i=0; i<n; i++) {
      x = (buf[j++]-xc)*xs;
      y = (buf[j++]-yc)*ys;
      z = (buf[j++]-zc)*zs;
      xx = x*a11 + y*a21 + z*a31;
      yy = x*a12 + y*a22 + z*a32;
      zz = x*a13 + y*a23 + z*a33;
      ff = zobs/(zobs-zz);
      pax[i] = xx*ff*mxx + mxb;
      pay[i] = yy*ff*myy + myb;
      paz[i] = zz;
    }
    pix.n = i;
    return n;
  }

  //************************************************************************************************
  //*************** The following pixel methods are not designed for View3D ************************

  @Override
  int rwc2pix (double[] rwc, MPoint pix, boolean rangeAdjust) {
    Midas.log("rwc2pix with range adjustment designed for latloncontinuous is NOT designed for View3D", Midas.WARNING);
    return rwc2pix(rwc,pix);
  }

  @Override
  int rwc2pix (double[] buf, int off, int dim, int npts, Pix pix, boolean isSpecializedFill) {
    // Use previous implementation of rwc2pix since 3.9.2
    return rwc2pix(buf,off,dim,npts,pix);
  }

  @Override
  int rwc2pixClip (float[] buf, int off, int dim, int npts, Pix pix, boolean mer) {
    Midas.log("rwc2pixClip is NOT designed for View3D", Midas.WARNING);
    return rwc2pix(buf,off,dim,npts,pix);
  }

  @Override
  int rwc2pixClip (double[] buf, int off, int dim, int npts, MPlot MP, Pix pix) {
    Midas.log("rwc2pixClip is NOT designed for View3D", Midas.WARNING);
    return rwc2pix(buf,off,dim,npts,pix);
  }

  @Override
  int rwc2pixClip (double[] buf, int off, int dim, int npts, Pix pix, boolean mer) {
    Midas.log("rwc2pixClip is NOT designed for View3D", Midas.WARNING);
    return rwc2pix(buf,off,dim,npts,pix);
  }

  @Override
  int rwc2pixClipAdjustForFill (double[] buf, int off, int dim, int npts, Pix pix, boolean mer) {
    Midas.log("rwc2pixClipAdjustForFill is NOT designed for View3D", Midas.WARNING);
    return rwc2pix(buf,off,dim,npts,pix);
  }

  int rwc2dpixClip (double[] buf, int off, int dim, int npts, MPlot MP, DPix pix) {
    Midas.log("rwc2dpixClip is NOT designed for View3D", Midas.WARNING);
    return rwc2dpix(buf, off, dim, npts, pix);
  }

  int rwc2dpixClip (double[] buf, int off, int dim, int npts, DPix pix, boolean mer) {
    Midas.log("rwc2dpixClip is NOT designed for View3D", Midas.WARNING);
    return rwc2dpix(buf, off, dim, npts, pix);
  }
}
