package nxm.sys.prim;

import nxm.sys.inc.Units;
import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.DataOp;
import nxm.sys.lib.Primitive;

/**
  Counts the number of a rake of values in a data file.
  <br>Note: This class extensively modified in 3.1.0 to support more
  data formats and provide better defaults and error handling.

  @author Jeff Schoen / JC4
*/
public class histogram extends Primitive {

  private DataFile hi;                  // input file
  private DataFile ho;                  // output file
  private Data data;                    // data buffer
  private double start;                 // The start value (i.e. the ~min value counted)
  private double end;                   // the end value (i.e. the ~max value counted)
  private double delta;                 // the delta between bins (end = start + delta*bins
  private int bins;                     // number of bins (equal to counts.length)
  private int[] counts;                 // the bins
  private float offset;                 // the offset value
  private float scale;                  // the scale value
  private boolean specialInf;           // special handling for +/- Infinity
  private boolean specialBit;           // special handling for bit data
  private long totalCounted;            // total points counted
  private long tooSmall;                // number of values not counted since they are below range
  private long tooLarge;                // number of values not counted since they are below range
  private boolean complex;              // complex mode
  private int cxmode=M_MAG;             // mode to use with complex input

  /** Mode Setting: Magnitude.               */
  public static final int M_MAG      = 1;
  /** Mode Setting: Phase.                   */
  public static final int M_PHASE    = 2;
  /** Mode Setting: Real.                    */
  public static final int M_REAL     = 3;
  /** Mode Setting: Imaginary.               */
  public static final int M_IMAG     = 4;

  /** Plot mode setting string list which corresponds to the Plot Mode Settings. <br>
      Possible values = <code>{@value}</code>
   */
  public final static String modeList = "Mag,Phase,Real,Imag";

  /** default constructor */
  public histogram () {}

  public int open () {

    hi = MA.getDataFile ("IN","1000","S#,C#",DataFile.INPUT);
    hi.open();

    start      = MA.getD("START");
    end        = MA.getD("END");
    delta      = MA.getD("DELTA");
    specialInf = false;
    specialBit = MA.getState("/BITS");
    bins       = MA.getL("BINS",-1);

    complex    = (hi.getFormatMode()=='C');
    if (complex) {
      cxmode = MA.getSelectionIndex("CXMODE",modeList,cxmode,0);
    }

    if (end <= start) {
      switch (hi.getFormatType()) {
        case DOUBLE : start = -Double.MAX_VALUE; end = Double.MAX_VALUE; specialInf = true; break;
        case FLOAT  : start = -Float.MAX_VALUE;  end = Float.MAX_VALUE;  specialInf = true; break;
        case XLONG  : start = Long.MIN_VALUE;    end = Long.MAX_VALUE;                      break;
        case LONG   : start = Integer.MIN_VALUE; end = Integer.MAX_VALUE;                   break;
        case INT    : start = Short.MIN_VALUE;   end = Short.MAX_VALUE;                     break;
        case BYTE   : start = Byte.MIN_VALUE;    end = Byte.MAX_VALUE;                      break;
        case NIBBLE : start = -8;                end = 7;                                   break;
        case PACKED : start = Byte.MIN_VALUE;    end = Byte.MAX_VALUE;   specialBit = true; break;
      }
    }

    if ((delta <= 0) && (bins <= 0)) {
      bins = 512;  // default number of bins
    }

    if (specialBit) {
      delta = 1;
      bins = 2;
    } else if (bins > 0) {
      delta = (end-start)/(bins-1);
    } else {
      bins = (int)((end-start)/delta)+1;  // +1 added to account for bin zero
    }

    scale  = (float)(1.0/delta);
    offset = (float)(start-(0.5/scale));

    ho = MA.getDataFile ("OUT","1000","SL",DataFile.OUTPUT);
    ho.setXUnits(Units.COUNTS);
    ho.setXStart(start);
    ho.setXDelta(delta);
    ho.setSize(bins);
    ho.open();

    xfer          = Math.max(1,(int)(bufSize/hi.dbpe));
    xfer          = MA.getL("/TL",xfer);
    counts        = new int[bins];
    totalCounted  = 0;
    tooSmall      = 0;
    tooLarge      = 0;

    if ((hi.getFormatType() == DOUBLE) || (hi.getFormatType() == XLONG)) {
      data = hi.getDataBuffer(xfer,DOUBLE);
    } else {
      data = hi.getDataBuffer(xfer,FLOAT);
    }

   // System.out.println("Histogram.open bins="+bins+" start="+start+" end="+end+" delta="+delta);  // debugging
   // for (int i=0; i<bins; i++) counts[i]=0;  // removed by J. Czechowski

    return (NORMAL);
  }

  public int process() {
    int n = hi.read(data);

    if (n<0) {
      return (FINISH);
    } else if (n==0) {
      return NOOP;
    }

    // for complex data, convert it prior to bin-ing
    if (complex) {
      switch (cxmode) { // Jeff, thanks for the fix to complex! NXM-4051
        case  M_MAG   : DataOp.mag(data, data, n, DataOp.CS);   break;
        case  M_PHASE : DataOp.phase(data, data, n, DataOp.CS); break;
        case  M_REAL  : DataOp.real(data, data, n, DataOp.CS);  break;
        case  M_IMAG  : DataOp.imag(data, data, n, DataOp.CS);  break;
        default       : DataOp.mag(data, data, n, DataOp.CS);   break;
      }
    }

    if (specialBit) {
      int bps = Data.getBPS(data.type);
      for (int i=0; i<(n*bps); i++) {
        // do a quick 'population count' (i.e. the number of bits set) on the octet and
        // then use that to increment the counters
        byte b = data.getB(i);

        int pop = ((b >> 0) & 0x1) + ((b >> 1) & 0x1) + ((b >> 2) & 0x1) + ((b >> 3) & 0x1) +
                  ((b >> 4) & 0x1) + ((b >> 5) & 0x1) + ((b >> 6) & 0x1) + ((b >> 7) & 0x1);

        counts[0] += 8 - pop;
        counts[1] += pop;
      }
      totalCounted += (n*bps);
    }
    else if (data.type == 'D') {
      double[] dbuf = data.castD(true);
      for (int i=0; i<n; i++) {
        double val = dbuf[i];
        int bin = (int)((val-offset)*scale);

        if      (bin < 0)     tooSmall++;
        else if (bin >= bins) tooLarge++;
        else                  counts[bin]++;
      }
      data.uncast(dbuf, false);
      totalCounted += n;
    }
    else {
      float[] fbuf = data.castF(true);
      for (int i=0; i<n; i++) {
        float val = fbuf[i];
        int bin = (int)( (val-offset)*scale );
        if      (bin < 0)     tooSmall++;
        else if (bin >= bins) tooLarge++;
        else                  counts[bin]++;
      }
      data.uncast(fbuf,false);
      totalCounted += n;
    }

    if (specialInf) {
      counts[0]      += tooSmall;  // count -Inf with MIN_VALUE
      counts[bins-1] += tooLarge;  // count +Inf with MAX_VALUE
      tooSmall       =  0;
      tooLarge       =  0;
    }

    if (hi.isPipe()) {
      ho.write(new Data(counts));
    }

    return (NORMAL);
  }

  public int close() {
    hi.close();
    ho.write(new Data(counts));
    ho.close();

    if (verbose) {
      long totalSkipped = tooSmall + tooLarge;
      long totalPoints  = totalCounted + totalSkipped;

   // since 3.7.1 use M.print instead of terminal.write to allow logging of interactive commands NXM-2813
      M.println("Input file            : " + hi.getName());
      M.println("Output file           : " + ho.getName());
      M.println("Start                 : " + start);
      M.println("End                   : " + end);
      M.println("Delta                 : " + delta);
      M.println("Bins                  : " + bins);
      M.println("Total points          : " + totalPoints);
      M.println("  Counted             : " + totalCounted);
      M.println("  Skipped (total)     : " + totalSkipped);
      M.println("  Skipped (N < start) : " + tooSmall);
      M.println("  Skipped (N > end)   : " + tooLarge);
    }

    return (NORMAL);
  }

}