package nxm.sys.lib;

import java.util.Arrays;

import nxm.sys.inc.Constants;
import nxm.sys.inc.DataTypes;
import nxm.sys.libm.Dbf;
import nxm.sys.libm.Fft;
import nxm.sys.libm.OpId;
import nxm.sys.libm.Operator;
import nxm.sys.libm.Operators;
import nxm.sys.libm.Window;

/**
  A class to define arithmetic operations on Data objects.
  <br><br>
  Each Data object has a data mode, type, and array length.
  <br><br>
  On non-unary functions, the array lengths must be equal to each other or equal to 1.
  In the latter case, the one element is treated as if it were duplicated N times,
  where N is the array length of the other arguments.

  @author Jeff Schoen
*/
public class DataOp extends Object implements OpId, Constants, DataTypes {

  /** data input/output modes S-scalar, C-complex, G-complexConjugate(flip sign of the imaginary part), V-vector(XYZ) */
  public static final int S=0x1, C=0x2, V=0x3, G=0xA,
                          SS=0x11, CS=0x12, SC=0x21, CC=0x22, CG=0xA2, VS=0x13, VV=0x33,
                          SSS=0x111, CSC=0x212, SCC=0x221, CCC=0x222, CGC=0x2A2, GCC=0xA22,
                          VSV=0x313, VVV=0x333, CCS=0x122, VVS=0x133, SSC=0x211, CSS=0x112,
                          SSSV=0x3111, VSSS=0x1113;

  /** windowing function name list */
  public static String windowList = Window.nameList;

  /** Cached set of operator instances. This reduces the creation of duplicate operator objects
   *  since all instances created by calling Operators.get(int) (as done below in run(..)) will
   *  be identical. Once an operator is inserted into this array, it is used read-only from the
   *  array; the only exception to this is the rare case case of two threads hitting run(..) at
   *  exactly the same time for an operator not already in the array, the result could be the
   *  creation of two duplicate operator object with one overwriting the other in the array.
   */
  private static Operator ops[] = new Operator[MAXOPS+1];

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

  /** Calls {@link nxm.sys.libm.Fft#process(Object, int, int)} to perform an inplace FFT
      @param A Data to be transformed
      @param size FFT size
      @param flag FFT flags
   */
  public synchronized static void fft (Data A, int size, int flag) {
    Fft.process (A.buf, size, flag);
  }

  /** Calls {@link nxm.sys.libm.Dbf#process(Object, int, int)} to perform an inplace dB calculation
      @param A Data to be transformed
      @param size dB size
      @param flag dB flags
   */
  public static void dbf (Data A, int size, int flag) {
    Dbf.process (A.buf, size, flag);
  }

  /** Get Window functioning unit
      @param name  name of Window
      @param size  size to be applied
      @param scale scale factor to be applied
      @param M     Midas context
      @return Data buffer
   */
  public static Data getWindow (String name, int size, double scale, Midas M) {
    Data data;
    float[] buf = Window.get(name,size,scale);
    if (buf!=null) {  // standard window
      data = new Data(buf);
    }
    else {            // assume non-standard window
      DataFile hcb = new DataFile();
      hcb.init (M,name,"1000","SF",0);
      hcb.open();
      data = hcb.getDataBuffer((int)hcb.getSize());
      hcb.read (data);
      hcb.close();
    }
    return data;
  }

// These versions are x10 slower (when measured in a profiler) than the versions
// below since they require "unnecessary" calls to cast/uncast.
//  /** zero out a Data object (offset and size in logical elements) */
//  public static void zero (Data A, int off, int n) {
//    fill (A,off,0.0,n);
//  }
//
//  /** fill a Data object (offset and size in logical elements) */
//  public static void fill (Data A, int off, double dval, int n) {
//    n = n*A.spa;
//    off = off*A.spa;
//    if (A.type==DOUBLE) {
//      double[] a=A.castD(true);
//      for (int i=off; i<off+n; i++) a[i]=dval;
//      A.uncast(a,true);
//    } else if (A.type==FLOAT) {
//      float fval = (float)dval;
//      float[] a=A.castF(true);
//      for (int i=off; i<off+n; i++) a[i]=fval;
//      A.uncast(a,true);
//    } else if (A.type==LONG) {
//      int lval = (int)dval;
//      int[] a=A.castL(true);
//      for (int i=off; i<off+n; i++) a[i]=lval;
//      A.uncast(a,true);
//    }
//  }

  /** zero out a Data object (offset and size in logical elements)
   *  @param A   Data object
   *  @param off offset
   *  @param n   size
   */
  public static void zero (Data A, int off, int n) {
    // We can bypass the cast/uncast since we already know all of the resulting
    // bytes will be 0.
    int bpe  = A.bpe;
    int boff = A.boff + (off * bpe);
    Arrays.fill(A.buf, boff, boff+n*bpe, (byte)0);
  }

  /** fill a Data object (offset and size in logical elements)
   *  @param A    Data objects
   *  @param off  offset
   *  @param dval data value
   *  @param n    size
   */
  public static void fill (Data A, int off, double dval, int n) {
    if (n < 1) return;
    if (dval == 0.0) {
      // Faster since we don't even need to pack+copy the first item!
      zero(A, off, n);
      return;
    }

    // We can bypass the cast/uncast and all of the packing (except the first) since
    // we are merely repeating values. We do this by packing the first value and
    // thereafter copying the packed bytes of that value to the rest of the buffer.
    int    boff;
    byte[] buf = A.buf;
    int    bpe = A.bpe;
    switch (A.type) {
      case DOUBLE: boff = A.boff+(off<<3); Convert.packD(buf, boff,        dval); break;
      case FLOAT:  boff = A.boff+(off<<2); Convert.packF(buf, boff, (float)dval); break;
      case LONG:   boff = A.boff+(off<<2); Convert.packL(buf, boff, ( int )dval); break;
      default:     return;
    }
    int start = boff + bpe; // skip first scalar
    int end   = boff + n*bpe;
    for (int i=start,j=0; i < end; i++,j++) {
      buf[i] = buf[boff+(j%bpe)];
    }
  }

  /** Copies a set of data from one {@link Data} object to another. This uses the default
      <code>boff</code> from both A and B; and uses <code>size</code> and <code>bpe</code>
      that match A.
      @param A Source for the copy.
      @param B Destination for the copy.
   */
  public static void copy (Data A, Data B) {
    System.arraycopy (A.buf,A.boff, B.buf,B.boff, A.size*A.bpe);
  }

  /** Copies a set of data from one {@link Data} object to another. This uses
      <code>bpe</code> that match A.
      @param A       Source for the copy.
      @param offA    Initial byte offset for A.
      @param B       Destination for the copy.
      @param offB    Initial byte offset for B.
      @param n       Number of repetitions.
   */
  public static void copy (Data A, int offA, Data B, int offB, int n) {
    System.arraycopy (A.buf,A.boff+offA*A.bpe, B.buf,B.boff+offB*A.bpe, n*A.bpe);
  }

  /** Copies a set of data from one {@link Data} object to another. With offset, stride,
      and element size all given.
      @param A       Source for the copy.
      @param offA    Initial byte offset for A.
      @param strideA Stride in bytes for A.
      @param B       Destination for the copy.
      @param offB    Initial byte offset for B.
      @param strideB Stride in bytes for B.
      @param bytes   Bytes-per-elements.
      @param n       Number of repetitions.
   */
  public static void copy (Data A, int offA, int strideA,
                           Data B, int offB, int strideB, int bytes, int n) {
    offA+=A.boff; offB+=B.boff;
    if (bytes>=16) {
      for (int j=0; j<n; j++) {
        System.arraycopy (A.buf,offA, B.buf,offB, bytes);
        offA += strideA; offB += strideB;
      }
    } else {
      for (int j=0; j<n; j++) {
        for (int i=0; i<bytes; i++) B.buf[offB++] = A.buf[offA++];
        offA += (strideA-bytes); offB += (strideB-bytes);
      }
    }
  }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Noop data operation
      @param A first Data object
      @param B second Data object
   */
  public static void noop (Data A, Data B) { run (NOOP, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Noop data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void noop (Data A, Data B, int n) { run (NOOP, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Noop data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void noop (Data A, Data B, int n, int mode) { run (NOOP, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform a Scale data operation
      @param A first Data object
      @param B second Data object
      @param scale the scale factor to apply
   */
  public static void scale (Data A, Data B, double scale) { run (SCALE, A,B,-1,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform a Scale data operation
      @param A     first Data object
      @param B     second Data object\
      @param n     return value if operation completes
      @param scale the scale factor to apply
   */
  public static void scale (Data A, Data B, int n, double scale) { run (SCALE, A,B,n,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform a Scale data operation
      @param A     first Data object
      @param B     second Data object
      @param n     return value if operation completes
      @param mode  the input/output mode
      @param scale the scale factor to apply
   */
  public static void scale (Data A, Data B, int n, int mode, double scale) { run (SCALE, A,B,n,mode,scale); }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Sum data operation
      @param A first Data object
      @param B second Data object
   */
  public static void sum (Data A, Data B) { run (SUM, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Sum data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void sum (Data A, Data B, int n) { run (SUM, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Sum data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void sum (Data A, Data B, int n, int mode) { run (SUM, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform an Add data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
   */
  public static void add (Data A, Data B, Data C) { run (ADD, A,B,C,-1,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform an Add data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
      @param n return value if operation completes
   */
  public static void add (Data A, Data B, Data C, int n) { run (ADD, A,B,C,n,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform an Add data operation
      @param A    first Data object
      @param B    second Data object
      @param C    third Data object
      @param n    return value if operation completes
      @param mode the input/output mode
 */
  public static void add (Data A, Data B, Data C, int n, int mode) { run (ADD, A,B,C,n,mode); }

  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Sub data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
   */
  public static void sub (Data A, Data B, Data C) { run (SUB, A,B,C,-1,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Sub data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
      @param n return value if operation completes
   */
  public static void sub (Data A, Data B, Data C, int n) { run (SUB, A,B,C,n,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Sub data operation
      @param A    first Data object
      @param B    second Data object
      @param C    third Data object
      @param n    return value if operation completes
      @param mode the input/output mode
    */
  public static void sub (Data A, Data B, Data C, int n, int mode) { run (SUB, A,B,C,n,mode); }

  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Mul data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
   */
  public static void mul (Data A, Data B, Data C) { run (MUL, A,B,C,-1,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Mul data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
      @param n return value if operation completes
   */
  public static void mul (Data A, Data B, Data C, int n) { run (MUL, A,B,C,n,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Mul data operation
      @param A    first Data object
      @param B    second Data object
      @param C    third Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void mul (Data A, Data B, Data C, int n, int mode) { run (MUL, A,B,C,n,mode); }

  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Div data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
   */
  public static void div (Data A, Data B, Data C) { run (DIV, A,B,C,-1,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Div data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
      @param n return value if operation completes
   */
  public static void div (Data A, Data B, Data C, int n) { run (DIV, A,B,C,n,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Div data operation
      @param A    first Data object
      @param B    second Data object
      @param C    third Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void div (Data A, Data B, Data C, int n, int mode) { run (DIV, A,B,C,n,mode); }

  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Pow data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
   */
  public static void pow (Data A, Data B, Data C) { run (POW, A,B,C,-1,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Pow data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
      @param n return value if operation completes
   */
  public static void pow (Data A, Data B, Data C, int n) { run (POW, A,B,C,n,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Pow data operation
      @param A    first Data object
      @param B    second Data object
      @param C    third Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void pow (Data A, Data B, Data C, int n, int mode) { run (POW, A,B,C,n,mode); }

  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Min data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
   */
  public static void min (Data A, Data B, Data C) { run (MIN, A,B,C,-1,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Min data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
      @param n return value if operation completes
   */
  public static void min (Data A, Data B, Data C, int n) { run (MIN, A,B,C,n,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Min data operation
      @param A    first Data object
      @param B    second Data object
      @param C    third Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void min (Data A, Data B, Data C, int n, int mode) { run (MIN, A,B,C,n,mode); }

  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Max data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
   */
  public static void max (Data A, Data B, Data C) { run (MAX, A,B,C,-1,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Max data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
      @param n return value if operation completes
   */
  public static void max (Data A, Data B, Data C, int n) { run (MAX, A,B,C,n,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Max data operation
      @param A    first Data object
      @param B    second Data object
      @param C    third Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void max (Data A, Data B, Data C, int n, int mode) { run (MAX, A,B,C,n,mode); }

  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Mux data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
   */
  public static void mux (Data A, Data B, Data C) { run (MUX, A,B,C,-1,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Mux data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
      @param n return value if operation completes
   */
  public static void mux (Data A, Data B, Data C, int n) { run (MUX, A,B,C,n,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Mux data operation
      @param A    first Data object
      @param B    second Data object
      @param C    third Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void mux (Data A, Data B, Data C, int n, int mode) { run (MUX, A,B,C,n,mode); }

  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Demux data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
   */
  public static void demux (Data A, Data B, Data C) { run (DEMUX, A,B,C,-1,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Demux data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
      @param n return value if operation completes
   */
  public static void demux (Data A, Data B, Data C, int n) { run (DEMUX, A,B,C,n,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Demux data operation
      @param A    first Data object
      @param B    second Data object
      @param C    third Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void demux (Data A, Data B, Data C, int n, int mode) { run (DEMUX, A,B,C,n,mode); }

  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Mod data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
   */
  public static void mod (Data A, Data B, Data C) { run (MOD, A,B,C,-1,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Mod data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
      @param n return value if operation completes
   */
  public static void mod (Data A, Data B, Data C, int n) { run (MOD, A,B,C,n,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Mod data operation
      @param A    first Data object
      @param B    second Data object
      @param C    third Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void mod (Data A, Data B, Data C, int n, int mode) { run (MOD, A,B,C,n,mode); }

  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Dot data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
   */
  public static void dot (Data A, Data B, Data C) { run (DOT, A,B,C,-1,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Dot data operation
      @param A first Data object
      @param B second Data object
      @param C third Data object
      @param n return value if operation completes
   */
  public static void dot (Data A, Data B, Data C, int n) { run (DOT, A,B,C,n,-1); }
  /** Calls {@link #run(int, Data, Data, Data, int, int)} to perform a Dot data operation
      @param A    first Data object
      @param B    second Data object
      @param C    third Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void dot (Data A, Data B, Data C, int n, int mode) { run (DOT, A,B,C,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Round data operation
      @param A first Data object
      @param B second Data object
   */
  public static void round (Data A, Data B) { run (ROUND, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Round data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void round (Data A, Data B, int n) { run (ROUND, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Round data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void round (Data A, Data B, int n, int mode) { run (ROUND, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Fix data operation
      @param A first Data object
      @param B second Data object
   */
  public static void fix (Data A, Data B) { run (FIX, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Fix data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void fix (Data A, Data B, int n) { run (FIX, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Fix data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void fix (Data A, Data B, int n, int mode) { run (FIX, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Mag data operation
      @param A first Data object
      @param B second Data object
   */
  public static void mag (Data A, Data B) { run (MAG, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Mag data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void mag (Data A, Data B, int n) { run (MAG, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Mag data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void mag (Data A, Data B, int n, int mode) { run (MAG, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Mag2 data operation
      @param A first Data object
      @param B second Data object
   */
  public static void mag2 (Data A, Data B) { run (MAG2, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Mag2 data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void mag2 (Data A, Data B, int n) { run (MAG2, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Mag2 data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void mag2 (Data A, Data B, int n, int mode) { run (MAG2, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Phase data operation
      @param A first Data object
      @param B second Data object
   */
  public static void phase (Data A, Data B) { run (PHASE, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Phase data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void phase (Data A, Data B, int n) { run (PHASE, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Phase data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void phase (Data A, Data B, int n, int mode) { run (PHASE, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Polar data operation
      @param A first Data object
      @param B second Data object
   */
  public static void polar (Data A, Data B) { run (POLAR, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Polar data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void polar (Data A, Data B, int n) { run (POLAR, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Polar data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void polar (Data A, Data B, int n, int mode) { run (POLAR, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Rect data operation
      @param A first Data object
      @param B second Data object
   */
  public static void rect (Data A, Data B) { run (RECT, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Rect data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void rect (Data A, Data B, int n) { run (RECT, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Rect data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void rect (Data A, Data B, int n, int mode) { run (RECT, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Real data operation
      @param A first Data object
      @param B second Data object
   */
  public static void real (Data A, Data B) { run (REAL, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Real data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void real (Data A, Data B, int n) { run (REAL, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Real data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output value
   */
  public static void real (Data A, Data B, int n, int mode) { run (REAL, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform an Imag data operation
      @param A first Data object
      @param B second Data object
   */
  public static void imag (Data A, Data B) { run (IMAG, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform an Imag data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void imag (Data A, Data B, int n) { run (IMAG, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform an Imag data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output value
   */
  public static void imag (Data A, Data B, int n, int mode) { run (IMAG, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Cmplx data operation
      @param A first Data object
      @param B second Data object
   */
  public static void cmplx (Data A, Data B) { run (CMPLX, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Cmplx data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void cmplx (Data A, Data B, int n) { run (CMPLX, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform a Cmplx data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void cmplx (Data A, Data B, int n, int mode) { run (CMPLX, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform an Ln data operation
      @param A first Data object
      @param B second Data object
   */
  public static void ln (Data A, Data B) { run (LN, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform an Ln data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void ln (Data A, Data B, int n) { run (LN, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform an Ln data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output value
   */
  public static void ln (Data A, Data B, int n, int mode) { run (LN, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int)} to perform an Exp data operation
      @param A first Data object
      @param B second Data object
   */
  public static void exp (Data A, Data B) { run (EXP, A,B,-1,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform an Exp data operation
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void exp (Data A, Data B, int n) { run (EXP, A,B,n,-1); }
  /** Calls {@link #run(int, Data, Data, int, int)} to perform an Exp data operation
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void exp (Data A, Data B, int n, int mode) { run (EXP, A,B,n,mode); }

  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Ln data operation with DB conversion
      @param A first Data object
      @param B second Data object
   */
  public static void dblog (Data A, Data B) { run (LN, A,B,-1,-1,LN2DB); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Ln data operation with DB conversion
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void dblog (Data A, Data B, int n) { run (LN, A,B,n,-1,LN2DB); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Ln data operation with DB conversion
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void dblog (Data A, Data B, int n, int mode) { run (LN, A,B,n,mode,LN2DB); }

  /** DB log bounded that is backwards-compatible with older versions
      Calls {@link #run(int, Data, Data, int, int, double)}
      @param A first Data object
      @param B second Data object
   */
  public static void dblogb (Data A, Data B) { run (LNB, A,B,-1,-1,LN2DB); }
  /** DB log bounded that is backwards-compatible with older versions
      Calls {@link #run(int, Data, Data, int, int, double)}
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void dblogb (Data A, Data B, int n) { run (LNB, A,B,n,-1,LN2DB); }
  /** DB log bounded that is backwards-compatible with older versions
      Calls {@link #run(int, Data, Data, int, int, double)}
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void dblogb (Data A, Data B, int n, int mode) { run (LNB, A,B,n,mode,LN2DB); }

  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Ln data operation with Log-10 conversion
      @param A first Data object
      @param B second Data object
   */
  public static void log10 (Data A, Data B) { run (LN, A,B,-1,-1,LN2LOG); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Ln data operation with Log-10 conversion
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void log10 (Data A, Data B, int n) { run (LN, A,B,n,-1,LN2LOG); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Ln data operation with Log-10 conversion
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void log10 (Data A, Data B, int n, int mode) { run (LN, A,B,n,mode,LN2LOG); }

  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Ln data operation with Log-20 conversion
      @param A first Data object
      @param B second Data object
   */
  public static void log20 (Data A, Data B) { run (LN, A,B,-1,-1,2*LN2LOG); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Ln data operation with Log-20 conversion
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void log20 (Data A, Data B, int n) { run (LN, A,B,n,-1,2*LN2LOG); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Ln data operation with Log-20 conversion
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void log20 (Data A, Data B, int n, int mode) { run (LN, A,B,n,mode,2*LN2LOG); }

  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Ln data operation with DB2 conversion
      @param A first Data object
      @param B second Data object
   */
  public static void dblogb2 (Data A, Data B) { run (LNB, A,B,-1,-1,2*LN2DB); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Ln data operation with DB2 conversion
      @param A first Data object
      @param B second Data object
      @param n return value if operation completes
   */
  public static void dblogb2 (Data A, Data B, int n) { run (LNB, A,B,n,-1,2*LN2DB); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Ln data operation with DB2 conversion
      @param A    first Data object
      @param B    second Data object
      @param n    return value if operation completes
      @param mode the input/output mode
   */
  public static void dblogb2 (Data A, Data B, int n, int mode) { run (LNB, A,B,n,mode,2*LN2DB); }

  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform a Sine Data operation
      @param A     first Data object
      @param B     second Data object
      @param scale the scale factor to apply
   */
  public static void sin (Data A, Data B, double scale) { run (SIN, A,B,-1,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform a Sine Data operation
      @param A     first Data object
      @param B     second Data object
      @param n     return value if operation completes
      @param scale the scale factor to apply
   */
  public static void sin (Data A, Data B, int n, double scale) { run (SIN, A,B,n,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform a Sine Data operation
      @param A     first Data object
      @param B     second Data object
      @param n     return value if operation completes
      @param mode  the input/output mode
      @param scale the scale factor to apply
   */
  public static void sin (Data A, Data B, int n, int mode, double scale) { run (SIN, A,B,n,mode,scale); }

  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform a Cosine Data operation
      @param A     first Data object
      @param B     second Data object
      @param scale the scale factor to apply
   */
  public static void cos (Data A, Data B, double scale) { run (COS, A,B,-1,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform a Cosine Data operation
      @param A     first Data object
      @param B     second Data object
      @param n     return value if operation completes
      @param scale the scale factor to apply
   */
  public static void cos (Data A, Data B, int n, double scale) { run (COS, A,B,n,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform a Cosine Data operation
      @param A     first Data object
      @param B     second Data object
      @param n     return value if operation completes
      @param mode  the input/output mode
      @param scale the scale factor to apply
   */
  public static void cos (Data A, Data B, int n, int mode, double scale) { run (COS, A,B,n,mode,scale); }

  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform a Tangent Data operation
      @param A     first Data object
      @param B     second Data object
      @param scale the scale factor to apply
   */
  public static void tan (Data A, Data B, double scale) { run (TAN, A,B,-1,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform a Tangent Data operation
      @param A     first Data object
      @param B     second Data object
      @param n     return value if operation completes
      @param scale the scale factor to apply
   */
  public static void tan (Data A, Data B, int n, double scale) { run (TAN, A,B,n,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform a Tangent Data operation
      @param A     first Data object
      @param B     second Data object
      @param n     return value if operation completes
      @param mode  the input/output mode
      @param scale the scale factor to apply
   */
  public static void tan (Data A, Data B, int n, int mode, double scale) { run (TAN, A,B,n,mode,scale); }

  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Arcsine Data operation
      @param A     first Data object
      @param B     second Data object
      @param scale the scale factor to apply
   */
  public static void asin (Data A, Data B, double scale) { run (ASIN, A,B,-1,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Arcsine Data operation
      @param A     first Data object
      @param B     second Data object
      @param n     return value if operation completes
      @param scale the scale factor to apply
   */
  public static void asin (Data A, Data B, int n, double scale) { run (ASIN, A,B,n,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Arcsine Data operation
      @param A     first Data object
      @param B     second Data object
      @param n     return value if operation completes
      @param mode  the input/output mode
      @param scale the scale factor to apply
   */
  public static void asin (Data A, Data B, int n, int mode, double scale) { run (ASIN, A,B,n,mode,scale); }

  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Arccosine Data operation
      @param A     first Data object
      @param B     second Data object
      @param scale the scale factor to apply
   */
  public static void acos (Data A, Data B, double scale) { run (ACOS, A,B,-1,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Arccosine Data operation
      @param A     first Data object
      @param B     second Data object
      @param n     return value if operation completes
      @param scale the scale factor to apply
   */
  public static void acos (Data A, Data B, int n, double scale) { run (ACOS, A,B,n,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Arccosine Data operation
      @param A     first Data object
      @param B     second Data object
      @param n     return value if operation completes
      @param mode  the input/output mode
      @param scale the scale factor to apply
   */
  public static void acos (Data A, Data B, int n, int mode, double scale) { run (ACOS, A,B,n,mode,scale); }

  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Arctangent Data operation
      @param A     first Data object
      @param B     second Data object
      @param scale the scale factor to apply
   */
  public static void atan (Data A, Data B, double scale) { run (ATAN, A,B,-1,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Arctangent Data operation
      @param A     first Data object
      @param B     second Data object
      @param n     return value if operation completes
      @param scale the scale factor to apply
   */
  public static void atan (Data A, Data B, int n, double scale) { run (ATAN, A,B,n,-1,scale); }
  /** Calls {@link #run(int, Data, Data, int, int, double)} to perform an Arctangent Data operation
      @param A     first Data object
      @param B     second Data object
      @param n     return value if operation completes
      @param mode  the input/output mode
      @param scale the scale factor to apply
   */
  public static void atan (Data A, Data B, int n, int mode, double scale) { run (ATAN, A,B,n,mode,scale); }

  /** Calls {@link #run(int, Data, Data, Data, int, int, double)} to perform an Arctangent2 Data operation
      @param A     first Data object
      @param B     second Data object
      @param C     third Data object
      @param scale the scale factor to apply
    */
  public static void atan2 (Data A, Data B, Data C, double scale) { run (ATAN2, A,B,C,-1,-1,scale); }
  /** Calls {@link #run(int, Data, Data, Data, int, int, double)} to perform an Arctangent2 Data operation
      @param A     first Data object
      @param B     second Data object
      @param C     third Data object
      @param n     return value if operation completes
      @param scale the scale factor to apply
   */
  public static void atan2 (Data A, Data B, Data C, int n, double scale) { run (ATAN2, A,B,C,n,-1,scale); }
  /** Calls {@link #run(int, Data, Data, Data, int, int, double)} to perform an Arctangent2 Data operation
      @param A     first Data object
      @param B     second Data object
      @param C     third Data object
      @param n     return value if operation completes
      @param mode  the input/output mode
      @param scale the scale factor to apply
   */
  public static void atan2 (Data A, Data B, Data C, int n, int mode, double scale) { run (ATAN2, A,B,C,n,mode,scale); }

  //////////////////////////////////////////////////////////////////////////////
  // RUN METHODS
  //////////////////////////////////////////////////////////////////////////////

  /** generic method for running an Operator on Data Objects
   *  @param opi    operator
   *  @param A      first Data object
   *  @param B      second Data object
   *  @param C      thrid Data object
   *  @param n      return value if operation completes
   *  @param mode   input/output mode
   *  @return number of atoms or specified return value
   */
  public static int run (int opi, Data A, Data B, Data C, int n, int mode) {
    if (n<0) n = A.size*A.ape;
    if (mode<0) mode = A.spa|(B.spa<<4)|(C.spa<<8);
    Operator op=ops[opi]; if (op==null) op=ops[opi]=Operators.get(opi);
    if (C.type==DOUBLE) {
      double[][] v = new double[3][];
      vcastD(A,B,C,v);
      n = op.process (v,n,mode);
      unvcastD(A,B,C,v);
    }
    else if (C.type==FLOAT) {
      float[][] v = new float[3][];
      vcastF(A,B,C,v);
      n = op.process (v,n,mode);
      unvcastF(A,B,C,v);
    }
    else {
      int[][] v = new int[3][];
      vcastL(A,B,C,v);
      n = op.process (v,n,mode);
      unvcastL(A,B,C,v);
    }
    return n;
  }

  /** generic method for running an Operator on Data Objects
   *  @param opi    operator
   *  @param A      first Data object
   *  @param B      second Data object
   *  @param C      third Data object
   *  @param n      return value if operation completes
   *  @param mode   input/output mod
   *  @param scale  the scale factor to apply
   *  @return number of atoms or specified return value
   */
  public static int run (int opi, Data A, Data B, Data C, int n, int mode, double scale) {
    if (n<0) n = A.size*A.ape;
    if (mode<0) mode = A.spa|(B.spa<<4)|(C.spa<<8);
    Operator op=ops[opi]; if (op==null) op=ops[opi]=Operators.get(opi);
    if (C.type==DOUBLE) {
      double[][] v = new double[3][];
      vcastD(A,B,C,v);
      n = op.process (v,n,mode,scale);
      unvcastD(A,B,C,v);
    }
    else if (C.type==FLOAT) {
      float[][] v = new float[3][];
      vcastF(A,B,C,v);
      n = op.process (v,n,mode,scale);
      unvcastF(A,B,C,v);
    }
    else {
      int[][] v = new int[3][];
      vcastL(A,B,C,v);
      n = op.process (v,n,mode,scale);
      unvcastL(A,B,C,v);
    }
    return n;
  }


  /** generic method for running an Operator on Data Objects
   *  @param opi     operator
   *  @param A       first Data object
   *  @param B       second Data object
   *  @param n       return value if operation completes
   *  @param mode    the input/output mode
   *  @return number of atoms or specified return value
   */
  public static int run (int opi, Data A, Data B, int n, int mode) {
    if (n<0) n = A.size*A.ape;
    if (mode<0) mode = A.spa|(B.spa<<4);
    Operator op=ops[opi]; if (op==null) op=ops[opi]=Operators.get(opi);
    if (B.type==DOUBLE) {
      double[][] v = new double[2][];
      vcastD(A,B,v);
      n = op.process (v,n,mode);
      unvcastD(A,B,v);
    }
    else if (B.type==FLOAT) {
      float[][] v = new float[2][];
      vcastF(A,B,v);
      n = op.process (v,n,mode);
      unvcastF(A,B,v);
    }
    else {
      int[][] v = new int[2][];
      vcastL(A,B,v);
      n = op.process (v,n,mode);
      unvcastL(A,B,v);
    }
    return n;
  }

  /** generic method for running an Operator on Data Objects with scaling
   *  @param opi     operator
   *  @param A       first Data object
   *  @param B       second Data object
   *  @param n       return value if operation completes
   *  @param mode    input/output mod
   *  @param scale   the scale factor to apply
   *  @return number of atoms or specified return value
   */
  public static int run (int opi, Data A, Data B, int n, int mode, double scale) {
    if (n<0) n = A.size*A.ape;
    if (mode<0) mode = A.spa|(B.spa<<4);
    Operator op=ops[opi]; if (op==null) op=ops[opi]=Operators.get(opi);
    if (B.type==DOUBLE) {
      double[][] v = new double[2][];
      vcastD(A,B,v);
      n = op.process (v,n,mode,scale);
      unvcastD(A,B,v);
    }
    else if (B.type==FLOAT) {
      float[][] v = new float[2][];
      vcastF(A,B,v);
      n = op.process (v,n,mode,scale);
      unvcastF(A,B,v);
    }
    else {
      int[][] v = new int[2][];
      vcastL(A,B,v);
      n = op.process (v,n,mode,scale);
      unvcastL(A,B,v);
    }
    return n;
  }

  //////////////////////////////////////////////////////////////////////////////
  // VECTOR CAST/UNCAST WITH 3 ARGUMENTS
  //////////////////////////////////////////////////////////////////////////////
  private static void vcastD (Data A, Data B, Data C, double[][] v) {
    v[0]=A.castD(true); v[1]=B.castD(true); v[2]=C.castD(false);
  }
  private static void unvcastD (Data A, Data B, Data C, double[][] v) {
    A.uncast(v[0],false); B.uncast(v[1],false); C.uncast(v[2],true);
  }
  private static void vcastF (Data A, Data B, Data C, float[][] v) {
    v[0]=A.castF(true); v[1]=B.castF(true); v[2]=C.castF(false);
  }
  private static void unvcastF (Data A, Data B, Data C, float[][] v) {
    A.uncast(v[0],false); B.uncast(v[1],false); C.uncast(v[2],true);
  }
  private static void vcastL (Data A, Data B, Data C, int[][] v) {
    v[0]=A.castL(true); v[1]=B.castL(true); v[2]=C.castL(false);
  }
  private static void unvcastL (Data A, Data B, Data C, int[][] v) {
    A.uncast(v[0],false); B.uncast(v[1],false); C.uncast(v[2],true);
  }

  //////////////////////////////////////////////////////////////////////////////
  // VECTOR CAST/UNCAST WITH 2 ARGUMENTS
  //////////////////////////////////////////////////////////////////////////////
  private static void vcastD (Data A, Data B, double[][] v) {
    v[0]=A.castD(true); v[1]=B.castD(false);
  }
  private static void unvcastD (Data A, Data B, double[][] v) {
    A.uncast(v[0],false); B.uncast(v[1],true);
  }
  private static void vcastF (Data A, Data B, float[][] v) {
    v[0]=A.castF(true); v[1]=B.castF(false);
  }
  private static void unvcastF (Data A, Data B, float[][] v) {
    A.uncast(v[0],false); B.uncast(v[1],true);
  }
  private static void vcastL (Data A, Data B, int[][] v) {
    v[0]=A.castL(true); v[1]=B.castL(false);
  }
  private static void unvcastL (Data A, Data B, int[][] v) {
    A.uncast(v[0],false); B.uncast(v[1],true);
  }
}
