package nxm.sys.libg;

import java.awt.Color;

import nxm.sys.inc.InternalUseOnly;
import nxm.sys.inc.PlotFile;
import nxm.sys.inc.Units;
import nxm.sys.lib.Args;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.Table;
import nxm.sys.prim.plot;

/**
  A class to hold and plot a 1 dimensional data layer.

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

  /** Since NeXtMidas 3.1.0 this field is now protected and for internal use only. */
  @InternalUseOnly
  protected float[] fbuf;
  /** Since NeXtMidas 3.1.0 this field is now protected, instead use {@link #getXIsTime()}. */
  protected boolean xIsTime;

  /** Default constructor. */
  public Layer1D () {
    super("1D");
  }

  @Override
  protected void init (plot p, String layerName, int layerIndex, Layer oldLayer) { // since 3.3.2
    super.init(p, layerName, layerIndex, oldLayer);
    getLine().setColor(layerIndex);

    switch (p.getPlotTypeID()) {
      case PlotFile.T_DOT:    getLine().setType(Line.DOT);   break;
      case PlotFile.T_POINT:  getLine().setType(Line.POINT); break;
      case PlotFile.T_RASTER: warning("Cannot Raster 1D files, use file{fs=size} to set frame size."); break;
    }
  }

  @Override
  public boolean setFile (PlotFile pf, Table tab, Args MA) {
    setFile(pf);
    setXAxis( pf.getXStart(), pf.getXDelta() );
    if (typeClass==1) {
      int ts=(int)Math.min(pf.getSize(),10.0e6);
      int fs=MA.getL("/FS",Math.min(ts,32768));
      setFrame(fs);
      int lps=MA.getL("/LPS",1);
      setSize(lps);
      setData (pf.getDataBuffer(fs*lps));
      isPaged = !realtime && (fs*lps)<ts;
      xIsTime = (pf.getXUnits()==Units.TIME);
      setTimeAlign(MA.getState("/TIMEALIGN",false));  // added for Bug 2419 in 3.1.2
    }
    else if (typeClass==2) {
      // NTN: 2011-06-07 next three line added to support plotting multi-line traces from type 2000 files (BUG 2397)
      ncolors = MA.getL("NC",32);
      cmap = MA.getSelectionIndex("CMAP", MColor.colorMapList, MColor.MULTI);
      colors = MColor.getColorMap(cmap, ncolors);

      setFrame( pf.getXFrame() );
      int lps=MA.getL("/LPS",1);
      if (lps == -1) lps = (int) pf.getSize(); // -1 means plot all line traces

      setSize(lps);
      setData(pf.getDataBuffer(lps));
      isPaged = false;
      setYUnits(Units.NONE); // does not apply here
    }
    else return false;
    return true;
  }

  @Override
  public void process () {
    ndata = 0; if (data==null) return;
    ndata = data.size*data.ape;
    fbuf = data2fa (data,ndata,fbuf,0,MP.mode);
  }

  @Override
  public void findRange() {
    if (frame==0) frame=ndata;
    x1 = xstart;
    x2 = xstart + (frame-1)*xdelta;
    if (x1>x2) { double t=x1; x1=x2; x2=t; }
      y1 = 0; y2 = 1;
    if (fbuf==null || fbuf.length == 0) return;
    int spa = (pf!=null)? pf.getMode() : 1;
    if (MP.mode==MPlot.M_RVI) {
      // since 2.7.1: BUG 70 - scale correctly for none scalar (e.g. complex) data when mode is Real vs Imaginary
      x1 = x2 = fbuf[0];
      y1 = y2 = fbuf[1];
      if (spa<2) spa = 2; // force spa=2 for RvI complex mode
      for (int ii=2; ii<(ndata*spa)-1 && ii<fbuf.length; ii+=spa) {
        if (fbuf[ii]>x2)   x2=fbuf[ii];
        if (fbuf[ii]<x1)   x1=fbuf[ii];
        if (fbuf[ii+1]>y2) y2=fbuf[ii+1];
        if (fbuf[ii+1]<y1) y1=fbuf[ii+1];
      }
    } else {
      y1 = y2 = fbuf[0];
      // for M_RNI (Real and Imaginary) we need to look at all the buffered data for non scalar data
      int elements = (MP.mode==MPlot.M_RNI) ? (ndata*spa) : ndata;
      for (int i=1; i<elements && i<fbuf.length; i++) {
        if (fbuf[i]>y2) y2=fbuf[i];
        if (fbuf[i]<y1) y1=fbuf[i];
      }
    }

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

  @Override
  public void findFullRange() { findFullRange(pf); }

  @Override
  public void findFullRange (PlotFile pf) {
    fx1 = x1; fx2 = x2;
    fy1 = y1; fy2 = y2;
    fz1 = z1; fz2 = z2;
    if (pf!=null && !realtime) {
      fx1 = pf.getXStart();
      // fix for Bug 1502 - plot/all does does not scale correctly
      if (typeClass==2) fx2 = fx1 + (pf.getXFrame()-1)*pf.getXDelta(); // 2D file (plotting one frame wide)
      else              fx2 = fx1 + (pf.getSize()-1)*pf.getXDelta();   // 1D file
      if (fx1>fx2) { double t=fx1; fx1=fx2; fx2=t; }
    }
  }

  @Override
  public void draw (int flag) {
    super.draw(flag);
    if (ndata <= 0) return; // nothing to draw

    int i1=0, i2=ndata, i12;
    // Begin Bug 2419 changes (since 3.1.0)
    double startPos = xstart;
    if (typeClass==1 && pf instanceof DataFile && pf.getUnits()==nxm.sys.inc.Units.TIME && isTimeAlign()) {
      double startTime =  pf.getTimeAt(xstart);
      Layer lay = MP.getBaseLayer();  // get the base layer
      if (lay!=null && lay!=this && lay.pf instanceof DataFile && pf instanceof DataFile && startTime>MP.rx1) {
        DataFile df = (DataFile)pf;
        startPos = ((DataFile)lay.pf).getIndex(startTime,DataFile.ISTART, DataFile.ADDR_ABSCISSA_AUTO) * df.getXDelta();
      }
    } // End changes for Bug 2419, except for changes below to MP.drawLine(..) calls

    if (size>0) i2=Math.min(frame,ndata);
    if (MP.view < MPlot.V_XYZ && MP.mode != MPlot.M_RVI) {
      if (xstart < MP.rx1)
        i1 = Math.max(0,(int)((MP.rx1-xstart)/xdelta ));
      if (xstart+ndata*xdelta > MP.rx2)
        i2 = Math.min(i2,(int)((MP.rx2-xstart)/xdelta )+2);
    }
    i12 = i2-i1;
    if (i12<=0) return;

    Line lin = line.copy();
    int spa = data.spa;

    if (size<=1) {
      if (MP.mode==MPlot.M_RVI) MP.drawLine (fbuf,i1*spa,spa,i12,lin);
      else if (MP.mode==MPlot.M_RNI && spa>1) {
        Color color1 = lin.getColor();
        for (int s=0; s<spa; ) {
          //MP.drawLine (fbuf,i1*spa+s,spa,i12,lin,xstart,xdelta);
          MP.drawLine (fbuf,i1*spa+s,spa,i12,lin,startPos,xdelta); // since 3.1.0: changed for Bug 2419
          lin.setColor( (++s<spa)? lin.getColor().darker() : color1 );
        }
      }
      //else MP.drawLine (fbuf,i1,1,i12,lin,xstart,xdelta);
      else MP.drawLine (fbuf,i1,1,i12,lin,startPos,xdelta);   // since 3.1.0: changed for Bug 2419
    }
    else for (int i=0; i<size; i++) {
      lin.setColor(colors[i%ncolors + 1]);
      if (MP.mode==MPlot.M_RVI) MP.drawLine (fbuf,(i1+i*frame)*2,2,i12,lin);
      //else MP.drawLine (fbuf,i1+i*frame,1,i12,lin,xstart-i*xdelta*frame,xdelta);
      else MP.drawLine (fbuf,i1+i*frame,1,i12,lin,startPos-i*xdelta*frame,xdelta); // since 3.1.0: changed for Bug 2419
    }
    if (feature!=null) feature.draw(flag);
  }

  @InternalUseOnly
  @Override
  public int drawDataSegment (Line lin, double start, double end) {
    if ((ndata <= 0) || (MP == null)) return 0;

    int i1 = Math.max(0,    (int)((start-xstart)/xdelta+.5));
    int i2 = Math.min(ndata,(int)((end  -xstart)/xdelta+.5));
    int npts = i2-i1;
    MP.drawLine(fbuf,i1,1,npts,lin,xstart,xdelta);
    return npts;
  }

  @Override
  public void setSize (int value) {
    super.setSize(value);
    if (size>1) setColorMap(-1,value);
  }

  @Override
  public double getPosAtTime (double time) {
    if (pf instanceof DataFile) {
      DataFile df  = (DataFile)pf;
      double delta = df.getDelta();
      double index = df.getIndexAt(time);
      return index * delta;
    } else {
      return super.getPosAtTime(time);
    }
  }

  @Override
  public double getTime (int px, int py) {
    // This method substantially re-written in NeXtMidas 2.8.1 for BUG 2063
    if ((MP.xvy && (py<MP.iy1 || py>MP.iy2)) || (px<MP.ix1 || px>MP.ix2)) return 0;  // outside plot bounds

    boolean invert  = (MP.view==MPlot.V_IXIY) || (MP.view==MPlot.V_IXY) || (MP.view==MPlot.V_IYIX) || (MP.view==MPlot.V_YIX);
    double  delta   = pf.getDelta();                         // x (or y) delta of the file
    double  xMinVal = (MP.rx1 < MP.orx1)? MP.orx1: MP.rx1;   // min x data value plotted on screen
    double  xMaxVal = (MP.rx2 > MP.orx2)? MP.orx2: MP.rx2;   // max x data value plotted on screen
    double  yMinVal = (MP.ry1 < MP.ory1)? MP.ory1: MP.ry1;   // min y data value plotted on screen
    double  yMaxVal = (MP.ry2 > MP.ory2)? MP.ory2: MP.ry2;   // max y data value plotted on screen
    int     pos;                                             // mouse position
    int     startPixel;                                      // pixel loc where the first data is drawn
    int     endPixel;                                        // pixel loc where the last data is drawn

    // which position to use (we only care about the X coord if view=YX, or the Y coord if view=XY)
    if (MP.xvy) {
      if (invert) pos = py;
      else        pos = (MP.pos.h - py);   // drawn from the bottom of the screen panel so flip it
    } else {
      if (invert) pos = (MP.pos.w - px);   // drawn from the right side of the panel so flip it
      else        pos = px;
    }

    /* JWB: We go through the somewhat onerous calculations below to get the startPixel and endPixel
       instead of simply using the plot data area boundaries (MP.ix1|ix2|iy1|iy2) because if the user
       ZOOMs across one of the borders of the plot, we plot a 'blank' area corresponding to the
       portion of the ZOOM dragbox that was outside the plot-able area. This 'blank' area
       is within the ix1|iy1...etc values but has no data so we need to disregard it.
     */
    double[] rwc = new double[2]; // used to store real-world coord for min/max mouse positions
    MPoint pix = new MPoint();    // used to store pixel location of RWC mouse position boundaries
    rwc[0] = xMinVal;
    rwc[1] = yMinVal;
    MP.viewer.rwc2pix(rwc, pix);  // start point

    /* get the pixel location of the start point */
    if (MP.xvy){
      if (invert) startPixel = pix.y;               // view= xvy and inverted so draw top->down
      else        startPixel = MP.pos.h - pix.y;    // view= xvy not inverted so draw bottom->up
    } else {
      if (invert) startPixel = MP.pos.w - pix.x;    // view= yx and inverted so draw right->left
      else        startPixel = pix.x;               // view= yx not inverted so draw left->right
    }

    rwc[0] = xMaxVal;
    rwc[1] = yMaxVal;
    MP.viewer.rwc2pix(rwc, pix);  // end point

    /* get the pixel location of the end point */
    if (MP.xvy) {
      if (invert) endPixel = pix.y;               // view= xvy and inverted so draw top->down
      else        endPixel = MP.pos.h - pix.y;    // view= xvy not inverted so draw bottom->up
    } else {
      if (invert) endPixel = MP.pos.w - pix.x;    // view= yx and inverted so draw right->left
      else        endPixel = pix.x;               // view= yx not inverted so draw left->right
    }

    if (pos < startPixel || pos > endPixel) return 0;                           // outside of drawn data area in plot
    double viewableIndex = ((xMaxVal - xMinVal) / delta) + 1;                   // viewable bounds (# of elements in view) (+1 added to include last element)
    double startIndex  = realtime? pf.seek() : (xMinVal - MP.orx1) / delta;     // index in file at start point of viewable area
    double relMousePos = (pos - (double)startPixel) / (endPixel - startPixel);  // percentage of mouse loc across the plot (0.000..1 - .9999999..)
    double index       = startIndex + (relMousePos * viewableIndex);            // index in file at given mouse position

    return pf.getTimeAt(index);
  }

  @Override
  public double getNearestYValue (int i) {
    if (fbuf==null) return 0.0;
    return (double)fbuf[i];
  }

  @Override
  public boolean clear () {
    ndata=0;
    return true;
  }

  /** Get if X units is time
   *  @return true if X units is time, otherwise false */
  public boolean getXIsTime () { // since 2.9.2
    return xIsTime;
  }

  @Override
  public Feature getFeature () {
    // This method added in 3.1.1 for Bug 2549. Only Layer1D Features should set the
    // Feature type to DATA, since it is unsupported in other Layers and causes a warning.
    if (feature!=null) return super.getFeature();
    else {
      feature = super.getFeature();
      feature.setType(Feature.DATA);  // Set Type to DATA for Layer1D
      return feature;
    }
  }

}
