package nxm.sys.libg;

import java.awt.Color;
import java.text.DecimalFormat;
import java.util.Arrays;

/**
  A class to hold and plot a grid layer.

  @author Jeff Schoen
*/
public class LayerGrid extends Layer {

  private int dim;
  private float[] fbuf;
  private DecimalFormat df = new DecimalFormat("#.##");

  /** Constructor */
  public LayerGrid () {
    super();
    setColorMap(MColor.GRID,-1);
    name = "GRID";
  }

  /** update View. */
  @SuppressWarnings("deprecation") // for usage of MPlot.viewLast until it becomes package-private in 3.7+
  public void update () {
    int n,nn,ic; double r,rmax;
    if (MP.view==MP.viewLast) return;
    ndata = 0;
    if (MP.view==MPlot.V_POLAR) {
      rmax = Math.max(Math.abs(MP.ory1),Math.abs(MP.ory2));
      fbuf = new float[1028*2];
      for (n=30; n<=90; n+=30) {
        ic=MColor.C_GRID; r = rmax*n/90;
        set2D (73,ic); set2D (-180.0,r); set2D (+180.0,r);
        for (nn=-180; nn<=180; nn+=5) set2D( (double)nn, r);
      }
      for (n=-180; n<=180; n+=30) {
        if (n==0) ic=MColor.C_AXIS; else ic=MColor.C_GRID;
        set2D (19,ic); set2D (n,0); set2D (n,90);
        for (nn=0; nn<=90; nn+=5) set2D( (double)n, rmax*nn/90);
      }
      dim=2;
    }
    else if (MP.view==MPlot.V_LATLON || MP.view==MPlot.V_MERCATOR || MP.view==MPlot.V_GEODETIC ||
        (MP.view==MPlot.V_LATLONCONTINUOUS)) {
      // LatLonContinuous mode will be processed by MPlot drawing, no need for adjustments here. Since 3.9.1
      fbuf = new float[4184*2];
      for (n=-75; n<=75; n+=15) {
        if (n==0) ic=MColor.C_AXIS; else ic=MColor.C_GRID;
        set2D (181,ic); set2D (-180,n); set2D (180,n);
        for (nn=-180; nn<=180; nn+=2) set2D(nn,n);
      }
      for (n=-180; n<=165; n+=15) {
        if (n==0) ic=MColor.C_AXIS; else ic=MColor.C_GRID;
        set2D (87,ic); set2D (n,-85); set2D (n,85);
        set2D (n,-85); for (nn=-84; nn<=84; nn+=2) set2D(n,nn); set2D (n,85);
      }
      dim=2;
    }
    else if (MP.view==MPlot.V_XYZ) {
      int np=5,np2=np*2;
      fbuf = new float[3*(3+np2+1)*3];
      double xc=0.0, xd=1.0/np;
      double yc=0.0, yd=1.0/np;
      double zc=0.0, zd=1.0/np;
      ic=MColor.C_AXIS;
      set3D (np2+1,ic,0); set3D(xc,yc,zc); set3D(xc+np*xd,yc,zc);
      for (n=-np; n<=np; n++) set3D(xc+n*xd,yc,zc);
      set3D (np2+1,ic,0); set3D(xc,yc,zc); set3D(xc,yc+np*yd,zc);
      for (n=-np; n<=np; n++) set3D(xc,yc+n*yd,zc);
      set3D (np2+1,ic,0); set3D(xc,yc,zc); set3D(xc,yc,zc+np*zd);
      for (n=-np; n<=np; n++) set3D(xc,yc,zc+n*zd);
      ic=MColor.C_GRID;
      dim=3;
    }
    else if (MP.view==MPlot.V_3DGRAPH) {
      // add a point to ndata so it draws later - kind of a hack for MPlot to draw layer
      ndata++;
    }
  }

  private void set2D (int x, int y) {
    fbuf[ndata++]=(float)x; fbuf[ndata++]=(float)y;
  }
  private void set2D (double x, double y) {
    fbuf[ndata++]=(float)x; fbuf[ndata++]=(float)y;
  }
  private void set3D (int x, int y, int z) {
    fbuf[ndata++]=(float)x; fbuf[ndata++]=(float)y; fbuf[ndata++]=(float)z;
  }
  private void set3D (double x, double y, double z) {
    fbuf[ndata++]=(float)x; fbuf[ndata++]=(float)y; fbuf[ndata++]=(float)z;
  }

  /** find layer bounds */
  @Override
  public void findRange() {
    super.findRange(); // start with default values
    if (MP.view==MPlot.V_LATLON || MP.view==MPlot.V_MERCATOR || MP.view==MPlot.V_GEODETIC) {
      x1=-180; x2=+180; y1=-90; y2=+90;
      // Set Minimum and Maximum actual auto-scale value (since 3.7.0 for NXM-2984)
      a1 = y1;
      a2 = y2;
    }
  }

  public void draw (int flag) {
    super.draw(flag);
    if (MP.view==MPlot.V_3DGRAPH) {
      draw3DGraphGrid();
      return;
    }
    if (ndata<=0) return;

    int k;
    Line lin = line.copy();
    int dim   = this.dim;                // since 3.5.2 make local copy of dimension field for thread safety for BUG 2337
    int ndata = this.ndata;              // since 3.5.2 make local copy of ndata field for thread safety for BUG 2337
    float[] fbufRef = this.fbuf;         // since 3.5.1 b/c update() can change the fbuf underneath this draw() for BUG 2337
    final int fbufLen = fbufRef.length;  // since 3.5.1
    final Color[] colors = this.colors;  // since 3.5.2 to prevent ArrayIndexOutOfBoundsException for BUG 2337
    final int colorsLen = colors.length; // since 3.5.2
    for (int j=0; j<ndata && (j+2)<fbufLen; j=k) { // since 3.5.1 check that j is in array bounds
      int npts = (int)fbufRef[j++];
      int ncol = (int)fbufRef[j++];      // color number
      if (dim>2) j += (dim-2);
      k = j + (2+npts)*dim;
      // Check if next j will be invalid due to changing values (zoom in, zoom out) since 3.5.2
      if (k < 0) {
        // If this warning never occurs, due to thread safety practices implemented in 3.5.2,
        // the warning can be removed in 3.9.x
        warning("LayerGrid.draw invalid next j of " + k + ": current j=" + j + " npts=" + npts
            + " dim=" + dim + " fbufLen=" + fbufLen + " ndata=" + data );
        break;
      }
      if (MP.view==MPlot.V_GEODETIC || MP.view==MPlot.V_XYZ || MP.view==MPlot.V_POLAR) {
        j += 2*dim;
      }
      else if (MP.view==MPlot.V_LATLONCONTINUOUS) {
        // Need a more complex check for in bounds with Lat Lon Continuous - since 3.9.0
        if ((j<fbufLen) && (fbufRef[j++]>=MP.rx2)) continue;
        if ((j<fbufLen) && (fbufRef[j++]>=MP.ry2)) continue;
        // Changed to use MP.orx values when in lat lon mode since 3.9.1
        if ((j<fbufLen) && (fbufRef[j]<=MP.orx1) && ((fbufRef[j]+360)>=MP.orx2)) continue;
        j++; // Incrementing here because the same index is checked twice in previous conditional
        if ((j<fbufLen) && (fbufRef[j++]<=MP.ry1)) continue;
      }
      else {
        // since 3.5.2 check that j<fbufLen to prevent potential ArrayIndexOutOfBoundsException
        if ((j<fbufLen) && (fbufRef[j++]>=MP.rx2)) continue;
        if ((j<fbufLen) && (fbufRef[j++]>=MP.ry2)) continue;
        if ((j<fbufLen) && (fbufRef[j++]<=MP.rx1)) continue;
        if ((j<fbufLen) && (fbufRef[j++]<=MP.ry1)) continue;
      }
      if (j>=fbufLen) break;      // since 3.5.1 prevent potential ArrayIndexOutOfBoundsException for BUG 2337
      if (ncol < colorsLen && ncol > 0) { // since 3.5.2 to prevent ArrayIndexOutOfBoundsException for BUG 2337
        lin.setColor(colors[ncol]);
      } else {  // warn if ncol is negative and/or out of range  since 3.5.2 for BUG 2337
        warning("LayerGrid.draw color number " + ncol + " is Out Of Range, using previous color "+ lin.getColor());
      }
      if (MP.view==MPlot.V_XYZ) MP.pix.setFlag(Pix.NORM);
      MP.drawLine (fbufRef,j,dim,npts,lin);
      if (MP.view==MPlot.V_XYZ) MP.pix.clrFlag(Pix.NORM);
    }
  }

  private void draw3DGraphGrid () {
    double[] centerPoints = {MP.view3D.xcenter,MP.view3D.ycenter,MP.view3D.zcenter,MP.view3D.xcenter,MP.view3D.ycenter,MP.view3D.zcenter};
    double[] ranges = {Math.abs(MP.orx1 - MP.orx2)/2,Math.abs(MP.ory1-MP.ory2)/2,Math.abs(MP.orz1-MP.orz2)/2};
    int numberOfLines = 5;
    Line lin = line.copy();
    Color origC = lin.getColor();
    lin.setColor(Color.gray);

    drawXYGrid(centerPoints, ranges[0], ranges[1], ranges[2], numberOfLines, lin, origC);
    drawYZGrid(centerPoints, ranges[0], ranges[1], ranges[2], numberOfLines, lin, origC);
    drawXZGrid(centerPoints, ranges[0], ranges[1], ranges[2], numberOfLines, lin, origC);
    MP.gc.setColor(origC);
  }

  private void drawXZGrid (double[] centerPoints, double xRange, double yRange, double zRange, int numberOfLines, Line lin, Color orig) {
    Pix pixCenter = new Pix(1, 0);
    MP.viewer.rwc2pix(centerPoints, 0, 3, 2, pixCenter);
    double xShift = xRange/numberOfLines;
    double zShift = zRange/numberOfLines;
    double yRangeAlt = 0;
    if (MP.view3D.needAltXZGrid()) {
      yRangeAlt = yRange;
    }
    Pix pixToDraw = new Pix(2, 0);
    for(int i=0; i <=numberOfLines; i++) {
      MP.setColor(Color.GRAY);
      // draw lines one direction
      double[] arr = new double[6];
      arr[0] = xRange;
      arr[1] = yRangeAlt;
      arr[2] = zShift * i;
      arr[4] = yRangeAlt;
      arr[5] = zShift * i;
      MP.viewer.rwc2pix(arr, 0, 3, 2, pixToDraw);
      MP.drawPixels(pixToDraw.x, pixToDraw.y, 2, lin);

      // draw lines other direction
      arr = new double[6];

      arr[0] = xShift * i;
      arr[1] = yRangeAlt;
      arr[2] = zRange;
      arr[3] = xShift * i;
      arr[4] = yRangeAlt;

      MP.viewer.rwc2pix(arr, 0, 3, 2, pixToDraw);
      MP.drawPixels(pixToDraw.x, pixToDraw.y, 2, lin);

      // Draw z axis values
      MP.setColor(orig);

      arr[0]=xRange + xShift;
      if (MP.view3D.needAltYZGrid()) arr[0] = 0 - xShift;
      arr[1] = yRangeAlt;
      arr[2] = zShift * i;
      arr[4] = yRangeAlt;

      MP.viewer.rwc2pix(arr, 0, dim, 2, pixToDraw);
      MP.gc.drawString(df.format(zShift * i), pixToDraw.x[0], pixToDraw.y[0]);
    }
    // Draw z axis label
    double[] arr = new double[6];
    arr[0] = xRange + xShift*2;
    if (MP.view3D.needAltYZGrid()) arr[0] = 0 - xShift*3;
    arr[1] = MP.view3D.needAltXZGrid() ? yRange : 0;
    arr[2] = zShift*((numberOfLines+1)/2);
    MP.viewer.rwc2pix(arr, 0, dim, 2, pixToDraw);
    String zLabel = (MP.getZAxisLabel().contentEquals("None (U)")) ? "Z" : MP.getZAxisLabel();
    int textAdjustment = (zLabel.length() - 1) * MP.tw;
    MP.gc.drawString(zLabel, pixToDraw.x[0] - textAdjustment, pixToDraw.y[0]);
  }

  private void drawYZGrid (double[] centerPoints, double xRange, double yRange, double zRange, int numberOfLines, Line lin, Color orig) {
    Pix pixCenter = new Pix(1, 0);
    MP.viewer.rwc2pix(centerPoints, 0, 3, 2, pixCenter);
    double xShift = xRange/numberOfLines;
    double yShift = yRange/numberOfLines;
    double zShift = zRange/numberOfLines;
    double zRangeAlt = 0;
    double xRangeAlt = 0;

    if (MP.view3D.needAltXYGrid()) {
      zRangeAlt = MP.view3D.needAltXZGrid() ? 0 : zRange;
    }

    if (MP.view3D.needAltYZGrid()) {
      xRangeAlt = xRange;
    }
    Pix pixToDraw = new Pix(2, 0);
    for(int i=0; i <=numberOfLines; i++) {
      MP.setColor(Color.GRAY);
      // Draw lines one direction
      double[] arr = new double[6];
      arr[0] = xRangeAlt;
      arr[1] = yShift * i;
      arr[2] = zRange;
      arr[3] = xRangeAlt;
      arr[4] = yShift * i;
      MP.viewer.rwc2pix(arr, 0, 3, 2, pixToDraw);
      MP.drawPixels(pixToDraw.x, pixToDraw.y, 2, lin);

      // Draw lines other direction

      arr = new double[6];
      arr[0] = xRangeAlt;
      arr[1] = yRange;
      arr[2] = zShift * i;
      arr[3] = xRangeAlt;
      arr[5] = zShift * i;
      MP.viewer.rwc2pix(arr, 0, 3, 2, pixToDraw);
      MP.drawPixels(pixToDraw.x, pixToDraw.y, 2, lin);

      // Draw y axis values
      MP.setColor(orig);
      arr[0]=xRange + xShift;
      if (MP.view3D.needAltYZGrid()) arr[0] = 0 - xShift;

      arr[1] = yShift*i;
      arr[2] = zRangeAlt;
      if (MP.view3D.needAltXYGrid() && MP.view3D.needAltXZGrid()) {
        arr[2] = zRange;
      }
      MP.viewer.rwc2pix(arr, 0, dim, 2, pixToDraw);
      MP.gc.drawString(df.format(yShift * i), pixToDraw.x[0], pixToDraw.y[0]);
    }

    // Draw y axis label
    double[] arr = new double[6];
    arr[0] = xRange + xShift*2;
    if (MP.view3D.needAltYZGrid()) arr[0] = 0 - xShift*2;
    arr[1] = yShift*((numberOfLines+1)/2);
    arr[2] = zRangeAlt;
    if (MP.view3D.needAltXYGrid() && MP.view3D.needAltXZGrid()) {
      arr[2] = zRange;
    }
    MP.viewer.rwc2pix(arr, 0, dim, 2, pixToDraw);
    String yLabel = (MP.getYAxisLabel().contentEquals("None (U)")) ? "Y" : MP.getYAxisLabel();
    int textAdjustment = (yLabel.length() - 1) * MP.tw;
    MP.gc.drawString(yLabel, pixToDraw.x[0] - textAdjustment, pixToDraw.y[0]);
  }

  private void drawXYGrid (double[] centerPoints, double xRange, double yRange, double zRange, int numberOfLines, Line lin, Color orig) {
    Pix pixCenter = new Pix(1, 0);
    MP.viewer.rwc2pix(centerPoints, 0, 3, 2, pixCenter);
    double xShift = xRange/numberOfLines;
    double yShift = yRange/numberOfLines;
    double zRangeAlt = 0;
    double labelShift = yShift*2;
    if (MP.view3D.needAltXYGrid()) zRangeAlt = zRange;
    Pix pixToDraw = new Pix(2, 0);
    for(int i=0; i <=numberOfLines; i++) {
      MP.setColor(Color.GRAY);

      // Draw lines one direction
      double[] arr = new double[6];
      arr[0] = xShift * i;
      arr[1] = yRange;
      arr[2] = zRangeAlt;
      arr[5] = zRangeAlt;
      arr[3] = xShift * i;
      MP.viewer.rwc2pix(arr, 0, 3, 2, pixToDraw);
      MP.drawPixels(pixToDraw.x, pixToDraw.y, 2, lin);
      // Draw lines another direction
      arr = new double[6];
      arr[0] = xRange;
      arr[1] = yShift * i;
      arr[2] = zRangeAlt;
      arr[4] = yShift * i;
      arr[5] = zRangeAlt;
      MP.viewer.rwc2pix(arr, 0, 3, 2, pixToDraw);
      MP.drawPixels(pixToDraw.x, pixToDraw.y, 2, lin);
      // Draw x axis values
      MP.setColor(orig);

      arr[0] = xShift*i;
      arr[1] = MP.view3D.needAltXZGrid() ? 0 - yShift*1.5 : yRange + yShift;
      arr[2] = zRangeAlt;
      MP.viewer.rwc2pix(arr, 0, dim, 2, pixToDraw);
      MP.gc.drawString(df.format(xShift * i), pixToDraw.x[0], pixToDraw.y[0]);
    }
    // Draw x axis label
    double[] arr = new double[6];
    arr[0] = xShift*((numberOfLines+1)/2);
    arr[1] = MP.view3D.needAltXZGrid() ? 0 - labelShift : yRange + labelShift;
    arr[2] = zRangeAlt;
    MP.viewer.rwc2pix(arr, 0, dim, 1, pixToDraw);
    String xLabel = (MP.getXAxisLabel().contentEquals("None (U)")) ? "X" : MP.getXAxisLabel();
    MP.gc.drawString(xLabel, pixToDraw.x[0], pixToDraw.y[0]);
  }

}
