package nxm.sys.libg;

import java.awt.Color;

import nxm.sys.inc.ListFile;
import nxm.sys.inc.PlotFile;
import nxm.sys.lib.Args;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.Table;

/**
  A class to hold and plot a X vs Y data layer.

  @author Jon Grasmeder, Jay Hallam
*/
public class LayerXY extends Layer {

  /** The buffer to be drawn: (x1,y1,x2,y2,...xn,yn)*/
  public double[] dbuf;
  private int colorIndex;
  private int xOffset;
  private int yOffset;
  private int nOffset;
  private char xFormat;
  private char yFormat;
  private char nFormat;
  private String tag;
  private Color[] colors;
  private String[] names; // holds names of x,y points - since 3.9.3

  private double xMin, yMin;
  private double xlMin, ylMin;
  /* x values need conversion to double */ private boolean xnotDouble = true;
  /* y values need conversion to double */ private boolean ynotDouble = true;
  private boolean haveNames = false; // names are listed of file

  /* Constructors */
  /** default constructor */
  public LayerXY () {
    super();
    line.setColor(MColor.getColorByIndex(1));
    enable &= ~MPlot.EN_LABELS; // Do not have labels on by default - since labels just added in 3.9.3
    super.name = "XY";
  }

  /** Set x-record name: TODO
   *  @param value unused as not yet implemented
   */
  public void setXRecName (String value) {
    //System.err.println("LayerXY.setXRecName not implemented");
  }

  /** Set x-record name: shorthand keyable "x=" syntax
   *  @param value x-record name */
  public void setX (String value) {
    setXRecName(value);
  }

  /** Set y-record name: TODO
   *   @param value unused as this is not yet implemented
   */
  public void setYRecName (String value) {
    //System.err.println("LayerXY.setYRecName not implemented");
  }

  /** Set y-record name: shorthand keyable "x=" syntax
   *  @param value y-record name */
  public void setY (String value) {
    setYRecName(value);
  }

  /** setup the file input - if necessary */
  public boolean setFile (PlotFile pf, Table tab, Args MA) {
    setFile(pf);
    boolean returnStatus = false;
    int xferLength = (int) pf.getSize(); //MA.getL("TL",1);

    if (pf instanceof DataFile) { // JC4 20MAR06: This is the OLD way of doing it.
      DataFile hcb = (DataFile)pf;
      if (hcb.typeClass==3) {
        Object xRec = tab.getKey("X");
        Object yRec = tab.getKey("Y");
        Object nRec = tab.containsKey("NAME") ? tab.getKey("NAME") : ""; // support for x,y names added in 3.9.3 for NXM_3495
        if (xRec != null && yRec != null) {
          String xRecName = ""+xRec;
          String yRecName = ""+yRec;
          String nRecName = ""+nRec;
          int xRecIndex = hcb.findRec(xRecName);
          int yRecIndex = hcb.findRec(yRecName);
          int nRecIndex = hcb.findRec(nRecName);
          if (xRecIndex >= 0 && yRecIndex >= 0) {
            setColumns(xRecIndex, yRecIndex, nRecIndex, hcb);
            setXAxis( 0, 1 ); //Initialize the x-axis start and delta
            setYAxis( 0, 1 ); //Initialize the y-axis start and delta
            Data dat = hcb.getDataBuffer( xferLength );
            setSize (dat.size);
            names = new String[dat.size];
            setData (dat);
          } else {
            warning("Unable to find X record '"+xRecName+"'" + " and/or Y record '"+yRecName+"' in header");
          }
        } else {
          warning("Need an X=TAG and Y=TAG for XY plot");
        }
      } else {
        warning("LayerXY plot for type 3000 file only");
      }
    } else if (pf instanceof ListFile) {
      ListFile lf = (ListFile)pf;

      String xRecName = tab.getS("X", "X");
      String yRecName = tab.getS("Y", "Y");
      String nRecName = tab.getS("LABEL", "NAME");  // support for x,y names added in 3.9.3 for NXM_3495

      tag = pf.getTag();
      super.name = tag;
      xOffset = 0;
      yOffset = 0;
      xFormat = ' ';
      yFormat = ' ';
      nOffset = 0;
      nFormat = ' ';
      // NXM-3495 393 set nOffset ?
      haveNames = false;

      for (int i = 0; i < lf.getRecordDefCount(); i++) {
        Table  recDef = lf.getRecordDef(i);
        String fmt    = recDef.getS("FORMAT", "XA");

        if (xRecName.equals(recDef.getS("NAME"))) {
          xFormat = fmt.charAt(1);
          xOffset = recDef.getL("OFFSET", xOffset); // Type 3000 has OFFSET, others use xOffset
        }

        if (yRecName.equals(recDef.getS("NAME"))) {
          yFormat = fmt.charAt(1);
          yOffset = recDef.getL("OFFSET", yOffset); // Type 3000 has OFFSET, others use yOffset
        }

        if (nRecName.equals(recDef.getS("NAME"))) {
          haveNames = true;
          nFormat = fmt.charAt(1);
          nOffset = recDef.getL("OFFSET", nOffset); // Type 3000 has OFFSET, others use nOffset
        }

        if (xFormat == ' ') { xOffset = xOffset + Data.getBPA(fmt); }
        if (yFormat == ' ') { yOffset = yOffset + Data.getBPA(fmt); }
        if (nFormat == ' ') { nOffset = nOffset + Data.getBPA(fmt); }
      }

      if (xFormat == ' ') {
        warning("LayerXY Can not find subrecord '"+xRecName+"' in "+this);
      }
      if (yFormat == ' ') {
        warning("LayerXY Can not find subrecord '"+yRecName+"' in "+this);
      }
      if (nFormat == ' ') {
        warning("LayerXY Can not find subrecord '"+nRecName+"' in "+this);
      }

      setXAxis( 0, 1 ); //Initialize the x-axis start and delta
      setYAxis( 0, 1 ); //Initialize the y-axis start and delta
      Data dat = pf.getDataBuffer(xferLength);
      setSize(dat.size);
      names = new String[dat.size];
      setData(dat);
    }

    return returnStatus;
  }

  public void process () {
    ndata = 0;
    if (data!=null) {
      ndata = data.size;
      dbuf = data2daXY(data,ndata,dbuf,MP.mode);
      if (haveNames)  data2names(data,ndata,dbuf,MP.mode); // create array of point names if names are available NXM-3495
      setXAxis( xMin, 1 ); //jph -- hard-wired
      setYAxis( yMin, 1 ); //jph -- hard-wired
    }
  }

  /** Convert the data into an (x1,y1,x2,y2,...xn,yn) array for plotting */
  private double[] data2daXY (Data data, int ndata, double[] dbuf, int mode) {
    double dbuftemp[] = new double[ndata];
    double scale   = setScale(data, mode);
    xMin = yMin = Double.MAX_VALUE;
    setxnotDouble(true);
    setynotDouble(true);

    // dbuf holds alternating x and y points
    if (dbuf==null||dbuf.length<ndata*2) dbuf = new double[ndata*2];

    if (xFormat == 'D') {
      for (int i=0; i < ndata; i++) dbuftemp[i] = data.unpackD(i*data.bpe + xOffset);
      for (int i=0; i < ndata; i++) if (dbuftemp[i]<xMin) xMin = dbuftemp[i];
      for (int i=0; i < ndata; i++) dbuf[i*2] = dbuftemp[i];
      setxnotDouble(false);
    }

    if (yFormat == 'D') {
      for (int i = 0; i < ndata; i++) dbuftemp[i] = data.unpackD(i*data.bpe + yOffset);
      for (int i = 0; i < ndata; i++) if (dbuftemp[i]<yMin) yMin = dbuftemp[i];
      for (int i = 0; i < ndata; i++) dbuf[i*2+1] = dbuftemp[i];
      setynotDouble(false);
    }

    if (getxnotDouble()) {
      for (int i = 0; i < ndata; i++)
        dbuf[i*2] = Convert.data2d(data, (i*data.bpe + xOffset), xFormat);
    }
    if (getynotDouble()) {
      for (int i = 0; i < ndata; i++)
        dbuf[i*2+1] = Convert.data2d(data, (i*data.bpe + yOffset), yFormat);
    }

    /** @todo this only scales the Y axis.  scale X axis as well? */
    switch (mode) {
      case MPlot.M_10LOG:
      case MPlot.M_20LOG:
        for (int i=0; i<ndata; i++) {
          int iod = i*2+1;
          int iev = i*2;
          dbuf[iod] = scale * Math.log(Math.max(10.e-11,Math.abs(dbuf[iod])));
        }
        break;
      default:
        break;
    }
    return dbuf;
  }

  /** Put point names in names array for plotting - since 3.9.3*/
  private void data2names (Data data, int ndata, double[] dbuf, int mode) {
    int len = Data.getBPA("XA");
    for (int i = 0; i < ndata; i++) {
      names[i] = data.unpackS(i*data.bpe + nOffset,len);
    }
  }

  /** find layer bounds - the x-min and y-min */
  public void findRange() {
    if (frame==0) frame=ndata;
    x1 = 0;
    x2 = 1;
    y1 = 0;
    y2 = 1;
    if (dbuf==null) return;
    x1 = x2 = dbuf[0];
    y1 = y2 = dbuf[1];
    for (int i=1; i<ndata; i++) {
      if (dbuf[i*2]  >x2) x2=dbuf[i*2];
      if (dbuf[i*2]  <x1) x1=dbuf[i*2];
      if (dbuf[i*2+1]>y2) y2=dbuf[i*2+1];
      if (dbuf[i*2+1]<y1) y1=dbuf[i*2+1];
    }
    if (MP.mode==MPlot.M_RVI) {  x1=y1; x2=y2; }

    autoScale4FindRange(y1, y2); // since 3.3.3 use helper method in super class

    setxlMin( xMin );
    setylMin( yMin );
  }

  public void draw (int flag) {
    super.draw(flag);
    if (ndata<=0) return;

    int i1=0;
    int i2=ndata;
    int i21;

    if (size>0) i2=frame;

    i21 = i2-i1;
    if (i21<=0) return;

    Line lin = line.copy();
    MP.drawLine(dbuf,0,2,data.size,lin,true); // since 3.9.1 for NXM-3131 enable high zoom code
    // draw names for points if LABELS are enabled and names available - since 3.9.3 for NXM-3495
    if (haveNames && ((enable & MPlot.EN_LABELS) != 0)) {
      MPoint pnt = new MPoint();
      for (int item = 0; item < names.length; item++) {
        MP.viewer.rwc2pix(new double[] {dbuf[item*2], dbuf[item*2+1]}, pnt, true);
        MP.gc.drawString(names[item], pnt.x + lin.getFont().getSize() + lin.getRadius(), pnt.y + lin.getRadius());
      }
    }

  }

  public void setSize (int value) {
    super.setSize(value);
    if (size>1) {
      cmap = MColor.MULTI;
      colors = MColor.getColorMap(cmap,size);
      line.setColor(colors[1]); // jph -- TBD
    }
  }

  /** Sets the class variables: tag, name, xOffset, yOffset, xFormat, yFormat
   *  @param x   index of the x offset
   *  @param y   index of the y offset
   *  @param hcb DataFile
   */
  public void setColumns (int x, int y, DataFile hcb) {
    tag     = hcb.getTag(); //jph -- 24mar04  + " ("+hcb.getRecName(x)+" v. "+hcb.getRecName(y)+")";
    super.name = tag;
    xOffset = hcb.getRecOffset(x);
    xFormat = (char) hcb.getRecFormatType(x);
    yOffset = hcb.getRecOffset(y);
    yFormat = (char) hcb.getRecFormatType(y);
  }

  /** Sets the class variables: tag, name, xOffset, yOffset, xFormat, yFormat
   *  @param x   index of the x offset
   *  @param y   index of the y offset
   *  @param n   index of the name offset
   *  @param hcb DataFile
   *  @since NeXtMidas 3.9.3 for NAM-3495
   */
  private void setColumns (int x, int y, int n, DataFile hcb) {
    setColumns(x,y,hcb);
    if (n >= 0) {
      haveNames = true;
      nOffset = hcb.getRecOffset(n);
      nFormat = (char) hcb.getRecFormatType(n);
    }
  }

  /** set color index
   *  @param value color index
   */
  public void setColorIndex (int value) {
    colorIndex = value;
  }

  /** get tag
   *  @return tag
   */
  public String getTag() {
    return tag;
  }

  /** get x min
   *  @return x min
   */
  public double getxMin() {
    return xMin;
  }

  /** get y min
   *  @return y min
   */
  public double getyMin() {
    return yMin;
  }

  /** get xl min
   *  @return xl min
   */
  public double getxlMin() {
    return xlMin;
  }

  /** get yl min
   *  @return yl min
   */
  public double getylMin() {
    return ylMin;
  }

  /** set xl min
   *  @param x xl min
   */
  public void setxlMin (double x) {
    xlMin = x;
  }

  /** set yl min
   *  @param x yl min
   */
  public void setylMin (double x) {
    ylMin = x;
  }

  /** get if x values are not stored as doubles
   *  @return true if they need conversion to doubles
   */
  public boolean getxnotDouble() {
    return xnotDouble;
  }

  /** get if y values are not stored as doubles
   *  @return true if they need conversion to doubles
   */
  public boolean getynotDouble() {
    return ynotDouble;
  }

  /** set if x values need conversion to double
   *  @param x true for x values not doubles
   */
  public void setxnotDouble (boolean x) {
    xnotDouble = x;
  }

  /** set y values need conversion to double
   *  @param x true for y values not doubles
   */
  public void setynotDouble (boolean x) {
    ynotDouble = x;
  }

}
