package nxm.sys.libg;

import nxm.sys.inc.Drawable;
import nxm.sys.inc.PlotFile;
import nxm.sys.inc.Units;
import nxm.sys.lib.Args;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Table;

/** An extension of {@link LayerMulti} that is designed to show the individual traces of a
    Type 2000 {@link DataFile} as separate lines. At this time only static Type 2000 plots
    are supported. <br>
    <br>
    If the file contains a <code>LEGEND</code> keyword in the extended header, it is used as
    a legend for the frames plotted (<code>LEGEND</code> must be an ASCII keyword which contains
    a comma-separated list of names). Otherwise, the rows are labeled "_N_" where N is the
    zero-based row number. The "_N_" syntax is modeled after that used in a LIST2 template.

    @author  J. Czechowski IV / Neon Ngo
 */
public class LayerMulti2D extends LayerMulti {
  // NOTE: This layer is heavilly based on LayerMulti and the structure of most of the
  //       methods follows what is in LayerMulti. In cases where specific differences
  //       are required (i.e. reading frame data), they typically follow Layer1D.

  protected float[] fbuf;      // Buffer of data to plot. (only for use within process and draw)
  protected boolean rescale;   // Is a rescale needed?    (only for use within process and getData)

  /** Constructors */
  public LayerMulti2D () {
    super();
    name = "Multi2D";
  }

  @Override
  public boolean setFile (PlotFile pf, Table tab, Args MA) {
    setFile(pf);

    if (!(pf instanceof DataFile)) {
      throw new MidasException("Only Type 2000 BLUE files are supported in "+name+".");
    }

    DataFile df = (DataFile)pf;

    if (df.typeClass != 2) {
      throw new MidasException("Only Type 2000 BLUE files are supported in "+name+".");
    }

    if (df.getSize() > Integer.MAX_VALUE) {
      // This limit is imposed by a number of places in LayerMulti2D and LayerMulti, changing this
      // would require some work. It is also worth considering if there is a good reason to exceed
      // this limit. File trimmers and other layers may be more appropriate for large files.
      throw new MidasException("Currently, "+name+" is limited to a max size of "+Integer.MAX_VALUE
                              +", given file of size "+df.getSize()+".");
    }

    int nels   = df.getFrameSize();
    int numRec = (int)df.getSize();

    setXAxis(pf.getXStart(), pf.getXDelta());
    setXUnits(pf.getXUnits());
    setYAxis(0, 1); // don't apply here
    setYUnits(Units.NONE); // don't apply here
    setColorMap(MColor.MULTI, numRec);

    recData  = null;                // not used (fbuf used in place of this)
    recNames = new String[numRec];  // allocate sub-records (columns) name mapping in file
    recIndex = 0;

    String legend = (String)df.getKeywordsObject().get("LEGEND");

    if (legend == null) {
      recNames = new String[0];
    }
    else {
      Parser parser = new Parser(legend);

      recNames = new String[parser.elements()];
      for (int i=0; i < recNames.length; i++) {
        recNames[i] = parser.get(i+1);
      }
    }
    showColsChanged = true; // Force a reset of the columns shown.

    if (df.isPipe()) {
      throw new MidasException("Pipes not supported in "+this.getClass());
    }

    int lpb = 1;                       // For now, just do one frame at a time
    setSize(lpb);                      // set Number of elements (rows) in the buffer
    setFrame(nels);                    // set frame size (DEF=number of rows (elements) in file)
    setData(pf.getDataBuffer(lpb));

    realtime   = false;
    isPaged    = (lpb < numRec);
    frameReady = false;
    frameDrawn = false;
    lastRow    = 0;

    return true;
  }

  public int getData (int mode) {
    int stat = Drawable.FULL;
    if (pf==null || !pf.isOpen()|| isDone || getDrawFrame()) return stat;

    if (!lock()) return stat;
    try {
      frameReady = false;
      frameDrawn = false;
      if (rereadFile) {
        pf.seek(0.0);
        ndata      = 0;
        rereadFile = false;
      }
      double avail = pf.avail();
      fseek   = pf.seek();
      rescale = (fseek == 0);

      while ((avail > 0) && !show((int)fseek)) { // skip hidden rows
        avail--;
        fseek++;
        pf.seek(fseek);
      }

      int ndo = (int)avail;
      if (avail > 0) { // find max ndo
        ndo = 1;
        while ((avail > ndo) && !show((int)fseek+ndo) && (ndo < data.getSize())) {
          ndo++;
        }
      }

      if (ndo>0) {                 // process new data
        int count = pf.read(data, ndo);
        process(count);

        if (getNeedRefresh()) stat = Drawable.DATA;  // draw full data if a refresh is needed
        else if (isReset)     stat = Drawable.DATA;
        else if (isPaged)     stat = Drawable.DATAX;
        else                  stat = defmode;

        frameReady = true;
      }
      else if (isPaged) {  // stop page reads
        pf.seek(pf.getSize());
        ndata=0;
      }
      else if (!realtime) {
        stat=Drawable.FULL;
      }
      else if (pf.isStream() && !pf.isStreaming()) {
        stat = Drawable.DONE;
      }
    } finally {
      unlock();
    }
    return stat;
  }

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

    if (rescale) {
      minY    = fbuf[0];
      maxY    = fbuf[0];
      rescale = false;
    }
    for (int i = 0; i < ndata*data.ape; i++) {
      float y = fbuf[i];
      if (y > maxY) maxY = y;
      if (y < minY) minY = y;
    }
    frameReady = true;
  }

  @Override
  public void draw (int flag) {
    if (fbuf == null) return;
    if (!lock()) return;
    try {
      clearNeedRefresh();
      isReset = false;
      if (flag!=Drawable.FULL && !getDrawFrame()) return;

      Line lin = line.copy();

      // loop through each trace and draw it's line-segments
      for (int i = 0; i < ndata; i++) {
        int irec = (int)fseek + i;
        lin.setColor(colors[irec%(colors.length-1)+1]);
        MP.drawLine(fbuf, i*data.ape, 1, data.ape, lin, xstart, xdelta);
      }
      frameDrawn = true;
    } finally {
      unlock();
    }
  }
}
