package nxm.sys.lib;

import nxm.sys.inc.InternalUseOnly;
import nxm.sys.inc.PacketHandler;

/** Defines the 64-octet header for ICE packetized data. The packet header looks like the following:
 *  <pre>
 *     +---------------+---------------+---------------+---------------+
 *  0  |  ICE_SYNC_1   |  ICE_SYNC_2   |  ICE_SYNC_3   |   User Key    |  ...
 *     +---------------+---------------+---------------+---------------+    :
 *  4  |                             Count                             |    :
 *     +---------------+---------------+---------------+---------------+    :
 *  8  |                 Size (data elements in packet)                |    :
 *     +---------------+---------------+---------------+---------------+    :
 * 12  |             User              |            Channel            |    :
 *     +---------------+---------------+---------------+---------------+    :
 * 16  |     ID #1     |     ID #2     | Rep ('I'/'E') |     BPA       |    :
 *     +---------------+---------------+---------------+---------------+    :
 * 20  |    Format ("SB","SI",etc.)    | Timecode Mode |Timecode Status|    :
 *     +---------------+---------------+---------------+---------------+    :  COMMON SUBRECORDS
 * 24  |                       Timecode Offset                         |    :
 *     |                                                               |    :
 *     |                                                               |    :
 *     +---------------+---------------+---------------+---------------+    :
 * 32  |                    Timecode Whole Seconds                     |    :
 *     |                                                               |    :
 *     |                                                               |    :
 *     +---------------+---------------+---------------+---------------+    :
 * 40  |                  Timecode Fractional Seconds                  |    :
 *     |                                                               |    :
 *     |                                                               |  ..:
 *     +---------------+---------------+---------------+---------------+
 * 48  |                          "User Data"                          |  ...
 *     |                                                               |    :
 *     | ABSC Mode:                  RamBuffer Mode:     User Mode:    |    :
 *     |  Abscissa Start (64-bit)     RBST (32-bit)       User defined |    :  ADJUNCT SUBRECORDS
 *     |  Abscissa Delta (64-bit)     RBSZ (32-bit)       128-bit data |    :
 *     |                              RBD0 (32-bit)                    |    :
 *     |                              RBDS (32-bit)                    |  ..:
 *     +---------------+---------------+---------------+---------------+
 *
 *      Notes:
 *       - BPA should be computed to match format
 *       - See {@link #setTC(int,int,double,double,double)} for list of
 *         valid Timecode Mode and Timecode Status values
 *       - Although ABSC Mode and User Mode are common, RamBuffer Mode
 *         is not
 *  </pre>
 *  @author Jeff Schoen
 *  @author Jay Hallam
 *  @author Neon Ngo - use specified data rep (byte order) when packing/unpacking to/from data buffer
 */
public class ICEPacket implements PacketHandler {
  /** The subrecord definitions common to SUBRECORDS_NORMAL and SUBRECORDS_ABSC. */
  private static final String SUBRECORDS_COMMON
      = "KEYS|4B,CNT|SL,ELEM|SL,USER|SI,CHAN|SI,SID|2B,"
      + "REP|SB,BPA|SB,MODE|SB,TYPE|SB,TCM|SB,TCS|SB,TCO|SD,TCWS|SD,TCFS|SD";

  /** The subrecord definitions (used with {@link DataFile#setSubRecords(String)})
      when *NOT* in {@link #ABSC} mode.
      @since NeXtMidas 3.3.0
   */
  public static final String SUBRECORDS_NORMAL = SUBRECORDS_COMMON + ",RBST|SL,RBSZ|SL,RBDO|SL,RBDS|SL";

  /** The subrecord definitions (used with {@link DataFile#setSubRecords(String)}) when in {@link #ABSC} mode.
      @since NeXtMidas 3.3.0
   */
  public static final String SUBRECORDS_ABSC   = SUBRECORDS_COMMON + ",XDEL|SD,XSTA|SD";

  /** Flag for using adjunct bytes as xdelta and xstart */
  public static final int ABSC=0x01;
  /** Flag for fixed size transfer length (constant packet size) */
  public static final int FIXED=0x02;
  /** The length of an ICE packet */
  public static final int headerLength=64;

  private int mhdrs=8;
  /** Byte buffer holding one or more ICE packets.  Meant to be read, not written */
  public byte[] buf = new byte[mhdrs*headerLength];

  private int nhdrs=0, ihdrs=0, ipass=0;
  private int fixedSize=0, bpa=1, ipoff=0, flags;
  private long count=0;

  private double npktoff;
  /** data representation/byte order of data/header in packet (defaults to local machine rep) */
  private byte   rep = Shell.rep;

  /* The ICE packet sync bytes */
  /** ICE SYNC 1 */ private static final int ICE_SYNC_1 = 101;
  /** ICE SYNC 2 */ private static final int ICE_SYNC_2 = 102;
  /** ICE SYNC 3 */ private static final int ICE_SYNC_3 = 103;

  /** header offset for ICE_SYNC_1 */
  public static final int HDR_ICE_SYNC_1     = 0;
  /** header offset for ICE_SYNC_2 */
  public static final int HDR_ICE_SYNC_2     = 1;
  /** header offset for ICE_SYNC_3 */
  public static final int HDR_ICE_SYNC_3     = 2;
  /** header offset for user key */
  public static final int HDR_USER_KEY       = 3;
  /** header offset for count */
  public static final int HDR_COUNT          = 4;
  /** header offset for size (data elements in packet) */
  public static final int HDR_SIZE_ELEM      = 8;
  /** header offset for user */
  public static final int HDR_USER           = 12;
  /** header offset for channel */
  public static final int HDR_CHANNEL        = 14;
  /** header offset for ID 1 */
  public static final int HDR_ID_1           = 16;
  /** header offset for ID 2 */
  public static final int HDR_ID_2           = 17;
  /** header offset for representation ('I'/'E') */
  public static final int HDR_REPRESENTATION = 18;
  /** header offset for bytes per atom */
  public static final int HDR_BPA            = 19;
  /** header offset for format ('SB, 'SI', etc.) */
  public static final int HDR_FORMAT         = 20;
  /** header offset for timecode mode */
  public static final int HDR_TC_MODE        = 22;
  /** header offset for timecode status */
  public static final int HDR_TC_STATUS      = 23;
  /** header offset for timecode offset */
  public static final int HDR_TC_OFFSET      = 24;
  /** header 3 (alternate) offset for header total frames */
  public static final int HDR_3_TOTAL_FRAMES = 24;
  /** header 3 (alternate) offset for frame count */
  public static final int HDR_3_FRAME_COUNT  = 26;
  /** header 3 (alternate) offset for first spare */
  public static final int HDR_3_SPARE_1      = 28;
  /** header 3 (alternate) offset for second spare */
  public static final int HDR_3_SPARE_2      = 30;
  /** header offset for timecode whole seconds */
  public static final int HDR_TC_WSEC        = 32;
  /** header offset for timecode fractional seconds */
  public static final int HDR_TC_FSEC        = 40;
  /** header offset for first user data (double or float) */
  public static final int HDR_USER_DATA      = 48;
  /** header offset for second user data float */
  public static final int HDR_USER_DATA_F2   = 52;
  /** header offset for second user data double */
  public static final int HDR_USER_DATA_D2   = 56;
  /** header offset for third user data float */
  public static final int HDR_USER_DATA_F3   = 56;
  /** header offset for forth user data float */
  public static final int HDR_USER_DATA_F4   = 60;

  /** XDelta is first 64-bits of User Data */
  @InternalUseOnly
  public static final int XDELTA_POSITION_IN_USER_DATA = 0;

  /** XDelta is second 64-bits of User Data */
  @InternalUseOnly
  public static final int XSTART_POSITION_IN_USER_DATA = 1;

  /** Maximum value of ICE Count is unsigned 0xFFFFFFFF
   *  @since 3.8.1
   */
  @InternalUseOnly
  public static final long MAX_COUNT = 4294967295l; // 0xFFFFFFFF

  private static final int BYTES_PER_SHORT    = Short.SIZE / Byte.SIZE;
  private static final int BYTES_PER_INTEGER  = Integer.SIZE / Byte.SIZE;
  private static final int BYTES_PER_FLOAT    = Float.SIZE / Byte.SIZE;
  private static final int BYTES_PER_DOUBLE   = Double.SIZE / Byte.SIZE;
  private static final int BYTES_PER_LONG     = Long.SIZE / Byte.SIZE;

  /** Construct an ICE packet 1024 bytes long */
  public ICEPacket () {
    this("SB",1024,0);
  }

  /** Construct an ICE packet from the specified byte array.
   *  @param ibuf   buffer containing ICE Packet information
   *  @param offset starting offset into buffer
   */
  public ICEPacket (byte[] ibuf,int offset) {
    setBytes(ibuf,offset);
  }

  /** set ICE packet from the specified byte array
   *  @param ibuf   buffer containing ICE Packet information
   *  @param offset starting offset into buffer
   */
  public void setBytes (byte[] ibuf,int offset) {
    System.arraycopy(ibuf,offset,buf,0,headerLength);
    // set internal fields (rep, bpa, count) based on packet header in byte array
    rep = getRep();
    bpa = getBPA();
    count = getUnsignedCount();
  }

  /** Construct an ICE packet
      @param format Midas format digraph used to set bpa, mode and type
      @param size The size in elements (of data)
      @param flags mask of flags for this ICEPacket (i.e. {@link #FIXED}, {@link #ABSC})
  */
  public ICEPacket (String format, int size, int flags) {
    this.flags = flags;
    setRep(Shell.rep); // default new packets to local machine rep
    setKeys((byte)0);
    setCount(0);
    if ((flags&FIXED)!=0) setFixedSize(size);
    else setSize(size);
    setUser((short)0);
    setChannel(1);
    setID((byte)0,(byte)0);
    setFormat(format);
    setTC((byte)0,(byte)0,0.0,0.0,0.0);
    if ((flags&ABSC)!=0) setAbscissa(0.0,1.0);
  }

  /** Set barker syncs and the user key.  Barker keys must be the same for all packets.
      @param userKey User defined key
  */
  public void setKeys (byte userKey) {
    buf[0]=ICE_SYNC_1; buf[1]=ICE_SYNC_2; buf[2]=ICE_SYNC_3;
    setUserKey(userKey);
  }

  /** Set the user defined key
   *  @param val the user defined key */
  public void setUserKey (byte val ) {
    buf[3] = val;
  }
  /** Get the user defined key
   *  @return the user defined key */
  public byte getUserKey () {
    return buf[3];
  }

  /** Does this packet have synchronization barker codes?
   *  @return true if the packet has synchronization barker codes */
  public boolean hasSync () {
    return hasSync(this.buf, 0);
  }

  /** Does the packet have synchronization barker codes?
      @param buf The buffer to check
      @param boffset Offset into the buffer
      @return true if the packet has synchronization barker codes
  */
  public static boolean hasSync (byte[] buf, int boffset) {
    return (buf[boffset+0] == ICE_SYNC_1) &&
           (buf[boffset+1] == ICE_SYNC_2) &&
           (buf[boffset+2] == ICE_SYNC_3);
  }

  /** packet number since start of xfer
   *  @param count packet number since start of xfer */
  public void setCount (int count) {
    this.count = count;
    Convert.packL(buf,HDR_COUNT,count,rep);
  }

  /** packet number since start of xfer
   *  @param count packet number since start of xfer
   *  @since NeXtMidas 3.9.1
   */
  public void setCount (long count) {
    this.count = count;
    Convert.packL(buf,HDR_COUNT,(int)count,rep);
  }

  /** up the count by one */
  public void upCount () {
    count++;
    Convert.packL(buf,HDR_COUNT,(int)count,rep);
  }
  /** up the count by N
   *  @param n amount to up the count */
  public void upCount (int n) {
    count+=n;
    Convert.packL(buf,HDR_COUNT,(int)count,rep);
  }
  /** data elements in packet
   *  @param size data elements in packet */
  public void setSize (int size) {
    Convert.packL(buf,HDR_SIZE_ELEM,size,rep);
  }
  /** Sets the user defined field.
   *  @param user the user defined field. */
  public void setUser (short user) {
    Convert.packI(buf,HDR_USER,user,rep);
  }
  /** Gets the user field. Since NeXtMidas 3.3.0.
   *  @return the user field */
  public short getUser () {
    return Convert.unpackI(buf,HDR_USER,rep);
  }
  /** port channel number
   *  @param channel channel number */
  public void setChannel (int channel) {
    Convert.packI(buf,HDR_CHANNEL,(short)channel,rep);
  }
  /** subsystem IDs
   *  @param id1 first system ID
   *  @param id2 second system ID
   */
  public void setID (byte id1, byte id2) {
    buf[HDR_ID_1]=id1;
    buf[HDR_ID_2]=id2;
  }

  /** set data representation ('I'=Ieee,'E'=Eeei,'V'=Vax), i.e. data byte order
   *  @param rep data representation */
  public void setRep (byte rep) {
    if (rep != this.rep) {
      Shell.getMidasContext().warning("Normally represention is the Shell default and is not set." +
          "  The represention is being changed from " + getRepString(this.rep) + " to "+getRepString(rep));
    }
    this.rep=rep;
    buf[HDR_REPRESENTATION]=this.rep;
  }

  /** get data representation ('I'=Ieee,'E'=Eeei,'V'=Vax), i.e. data byte order. @since NeXtMidas 2.7.1
   *  @return data representation */
  public byte getRep () {
    return buf[HDR_REPRESENTATION];
  }
  /** get data representation ('I'=Ieee,'E'=Eeei,'V'=Vax), i.e. data byte order of
   *  specified ICE packet in specified byte array.
   *  @param buf     buffer to grab representation from
   *  @param boffset offset beyond header representation
   *  @return data representation
   *  @since NeXtMidas 2.7.1
   */
  public static byte getRep (byte[] buf, int boffset) {
    return buf[boffset+HDR_REPRESENTATION];
  }

  /** Get bytes per atom (BPA). @since NeXtMidas 2.7.3
   *  @return bytes per atom (BPA). */
  public int getBPA () { return buf[19]; }
  /** Set bytes per atom (BPA). For advanced usage only as {@link #setFormat(String)}
      calculates and call this method to set bpa appropriately.
      Note this will downcast int to a byte to put into packet header.
      @param val bytes per atom (BPA)
      @since NeXtMidas 2.7.3
   */
  public void setBPA (int val) { bpa = buf[HDR_BPA] = (byte)val; }

  /** Set data format
      @param format Midas format digraph (e.g. "SB", "SI", etc.)
   */
  public void setFormat (String format) {
    setBPA(Data.getBPA(format));
    buf[HDR_FORMAT] = (byte)format.charAt(0);
    buf[HDR_FORMAT+1] = (byte)format.charAt(1);
  }

  /** Set timecode mode, status, offset whole seconds and fractional seconds
      @param mode Timecode mode (0=OFF, 1=CPU, 2=ZTC, 3=SDN, 4=SMS, 5=DTL, 6=IRD, 7=SDDS)
      @param status Timecode status (2=OKNC, 1=OK, -1=InActive, -2=BadParam, -3=NoBits, -4=NoBarker,
                                    -5=BCDErr, -6=NAN, -7=NoClock, -8=BadStart, -9=NTPErr)
      @param offset Offset at which timecode applies
      @param wsec Timecode whole seconds
      @param fsec Timecode fractional seconds
  */
  public void setTC (int mode, int status, double offset, double wsec, double fsec) {
    buf[HDR_TC_MODE]=(byte)mode;
    buf[HDR_TC_STATUS]=(byte)status;
    Convert.packD(buf,HDR_TC_OFFSET,offset,rep);
    Convert.packD(buf,32,wsec,rep);
    Convert.packD(buf,40,fsec,rep);
  }

  /** Get timecode as a Time object
   *  @return timecode as a Time object */
  public Time getTC () {
    double wsec = Convert.unpackD(buf,HDR_TC_WSEC,rep);
    double fsec = Convert.unpackD(buf,HDR_TC_FSEC,rep);
    return new Time (wsec,fsec);
  }

  /** Get original whole seconds from timecode.
   *  Most users will call {@link #getTC()}
   *  @return original whole seconds from timecode.
   *  @since NeXtMidas 3.4.2
   */
  public double getTCWSec () {
    return Convert.unpackD(buf,HDR_TC_WSEC,rep);
  }

  /** Get original fractional seconds from timecode.
   *  Most users will call {@link #getTC()}
   *  @return original fractional seconds from timecode.
   *  @since NeXtMidas 3.4.2
   */
  public double getTCFSec () {
    return Convert.unpackD(buf,HDR_TC_FSEC,rep);
  }

  /** get TC offset
   *  @return TC offset */
  public double getTCO () {
    return Convert.unpackD(buf,HDR_TC_OFFSET,rep);
  }

  /** Gets the TC mode. Since NeXtMidas 3.3.0.
   *  @return TC mode */
  public int getTCMode () { return buf[HDR_TC_MODE]; }

  /** Gets the TC status. Since NeXtMidas 3.3.0.
   *  @return TC status */
  public int getTCStatus () { return buf[HDR_TC_STATUS]; }

  /** decrement TC offset
   *  @param xfer amount to decrement by */
  public void decTCO (int xfer) {
    double tcoff = Convert.unpackD(buf,HDR_TC_OFFSET,rep);
    Convert.packD(buf,HDR_TC_OFFSET,tcoff-xfer,rep);
  }

  /** Get timecode
      @param tc Time value to set
      @param offset Sample offset
      @param delta Sample delta
      @return Timecode status
  */
  public int getTC (Time tc, double offset, double delta) {
    double wsec = Convert.unpackD(buf,HDR_TC_WSEC,rep);
    double fsec = Convert.unpackD(buf,HDR_TC_FSEC,rep);
    tc.fromJ1950( wsec,fsec );
    double tcoff = offset - Convert.unpackD(buf,HDR_TC_OFFSET,rep);
    tc.addSec( tcoff*delta );
    return (int)buf[HDR_TC_STATUS]; // timecode status
  }

/* Start of set/get User Data */
  /** Set the user data.
      @param val The value
      @param pos The element position in the user data area (0-1)
  */
  public void setUserData (double val, int pos) {
    Convert.packD(buf,HDR_USER_DATA+(BYTES_PER_DOUBLE*pos),val,rep);
  }
  /** Set the user data.
      @param val The value
      @param pos The element position in the user data area (0-1)
  */
  public void setUserData (long val, int pos) {
    Convert.packX(buf,HDR_USER_DATA+(BYTES_PER_LONG*pos),val,rep);
  }
  /** Set the user data.
      @param val The value
      @param pos The element position in the user data area (0-3)
  */
  public void setUserData (float val, int pos) {
    Convert.packF(buf,HDR_USER_DATA+(BYTES_PER_FLOAT*pos),val,rep);
  }
  /** Set the user data.
      @param val The value
      @param pos The element position in the user data area (0-3)
  */
  public void setUserData (int val, int pos) {
    Convert.packL(buf,HDR_USER_DATA+(BYTES_PER_INTEGER*pos),val,rep);
  }
  /** Set the user data.
      @param val The value
      @param pos The element position in the user data area (0-8)
  */
  public void setUserData (short val, int pos) {
    Convert.packI(buf,HDR_USER_DATA+(BYTES_PER_SHORT*pos),val,rep);
  }
  /** Set the user data.
      @param val The value
      @param pos The element position in the user data area (0-16)
  */
  public void setUserData (byte val, int pos) {
    buf[HDR_USER_DATA+pos] = val;
  }

  /** Get the user data as a double
      @param pos The element position in the user data area (0-1)
      @return  user data as a double
  */
  public double getUserDataD (int pos) {
    return Convert.unpackD(buf,HDR_USER_DATA+(BYTES_PER_DOUBLE*pos),rep);
  }
  /** Get the user data as a Midas X extra long type (Java long)
      @param pos The element position in the user data area (0-1)
      @return user data as a Midas X extra long type (Java long)
  */
  public long getUserDataX (int pos) {
    return Convert.unpackX(buf,HDR_USER_DATA+(BYTES_PER_LONG*pos),rep);
  }
  /** Get the user data as a float
      @param pos The element position in the user data area (0-3)
      @return user data as a float
  */
  public float getUserDataF (int pos) {
    return Convert.unpackF(buf,HDR_USER_DATA+(BYTES_PER_FLOAT*pos),rep);
  }
  /** Get the user data as a Midas L type (Java int)
      @param pos The element position in the user data area (0-3)
      @return user data as a Midas L type (Java int)
  */
  public int getUserDataL (int pos) {
    return Convert.unpackL(buf,HDR_USER_DATA+(BYTES_PER_INTEGER*pos),rep);
  }
  /** Get the user data as a Midas I type (Java short)
      @param pos The element position in the user data area (0-8)
      @return user data as a Midas I type (Java short)
  */
  public short getUserDataI (int pos) {
    return Convert.unpackI(buf,HDR_USER_DATA+(BYTES_PER_SHORT*pos),rep);
  }
  /** Get the user data as byte
      @param pos The element position in the user data area (0-16)
      @return user data as byte
  */
  public byte getUserDataB (int pos) {
    return buf[HDR_USER_DATA+pos];
  }

/* End of set/get User Data */

  /** set abscissa xstart and xdelta
      @param xstart The x start
      @param xdelta The x delta
  */
  public void setAbscissa (double xstart, double xdelta) {
    Convert.packD(buf,HDR_USER_DATA,xdelta,rep);
    Convert.packD(buf,HDR_USER_DATA+BYTES_PER_DOUBLE,xstart,rep);
  }

  /** set abscissa mode
      @param mode The special header mode */
  public void setMode (String mode) {
    if (mode.equals("ABSC")) flags |= ABSC; else flags &= ~ABSC;
  }

  /** Set fixed size mode
      @param size The desired fixed size */
  public void setFixedSize (int size) {
    fixedSize = size;
    setSize(size);
  }

  /** Get Channel number of this packet
   *  @return Channel number of this packet */
  public int getChannel () {
    return (int)Convert.unpackI(buf,HDR_CHANNEL,rep);
  }

  /** Get the current packet count (see {@link #getUnsignedCount()})
   *  @return current packet count as int (could overflow)
   */
  public int getCount () {
    count = Convert.unpackL(buf,BYTES_PER_INTEGER,rep);
    return (int)count;
  }

  /** Get unsigned current packet count
   *  @return unsigned current packet count
   */
  @InternalUseOnly
  public long getUnsignedCount () {
    count = Convert.unpackL(buf,BYTES_PER_INTEGER,rep);
    if (count < 0) count = MAX_COUNT + 1 + count; // create unsigned count
    return count;
  }

  /** get the current Midas digraph format
   *  @return Midas digraph format */
  public String getFormat () {
    return Convert.unpackS(buf,HDR_FORMAT,BYTES_PER_SHORT);
  }

  /** Get Size of packet data in elements
   *  @return size of packet data in elements */
  public int getSize () {
    int size;
    if (fixedSize!=0) size = fixedSize;
    else size = Convert.unpackL(buf,BYTES_PER_LONG,rep);
    return size;
  }

  /** Get size of packet data in bytes
   *  @return size of packet data in bytes */
  public int getBytes () {
    int bytes = bpa*Convert.unpackL(buf,BYTES_PER_LONG,rep);
    return bytes;
  }
  /** get fixed size
   *  @return fixed size
   */
  public int getFixedSize() {
    return fixedSize;
  }

  /** Not Used - Use user data as a ram buffer
   *  @param off   offset
   *  @param value value
   *  @deprecated in NeXtMidas 3.9.1 - marked not used in 2006
   */
  @Deprecated
  public void setRBData (int off, int value) {
    Convert.packL(buf,HDR_USER_DATA+off*BYTES_PER_INTEGER,value,rep);
  }
  /** Not Used - Use user data as a ram buffer
   *  @param off offset
   *  @return user data
   *  @deprecated in NeXtMidas 3.9.1 - marked not used in 2006
   */
  @Deprecated
  public int getRBData (int off) {
    return Convert.unpackL(buf,HDR_USER_DATA+off*BYTES_PER_INTEGER,rep);
  }

  //////////////////////////////////////////////////////////////////////////////
  // packet Handler methods
  //////////////////////////////////////////////////////////////////////////////
  @Override
  public String getConfiguration (DataFile df) {
    String config = "ICE";
    if (fixedSize!=0) config += "/FS="+fixedSize;
    if ((flags&ABSC)!=0) config += "/MODE=ABSC";
    if (df.hp!=null) {
      config += "/DET";
      String aux = df.hp.getAux();
      if (aux != null && aux.length() > 0) config += "=" + aux;
    }
    return config;
  }

  @Override
  public void setFileName (DataFile df, FileName filename) {
    DataFile hp = new DataFile();
    hp.init(df.cmd,filename,"3000","NH", df.getPacketHandlerFlags() );
    if ((flags&ABSC)!=0) hp.setSubRecords(SUBRECORDS_ABSC);
    else  hp.setSubRecords(SUBRECORDS_NORMAL);
    if (fixedSize>0) hp.setSize((int)(df.getSize()/fixedSize));
    df.hp=hp;
  }

  @Override
  public double naturalDataOffset (DataFile df, double boffset) {
    int bpp = fixedSize*bpa;
    if (bpp==0) bpp = getBytes();
    double npkt = Math.floor(boffset/bpp);
    return npkt*bpp;
  }

  @Override
  public double dataToPacketOffset (DataFile df, double boffset) {
    int bpp = fixedSize*bpa;
    if (bpp==0) bpp = getBytes();
    double npkt = Math.floor(boffset/bpp);
    double bpkthdr = npkt*headerLength;
    ipoff = (int)(boffset-npkt*bpp);            // save offset into packet
    if (df.hp==null) {
      boffset += bpkthdr;
      if (ipoff>0) boffset += headerLength;     // skip header
    } else {
      if (ipoff>0) npkt++;                      // skip header
      npktoff = npkt;
    }
    return boffset;
  }

  @Override
  public double packetToDataOffset (DataFile df, double boffset) {
    if (df.hp!=null) return boffset;
    int bpp = fixedSize*bpa;
    if (bpp==0) bpp = getBytes();
    double npkt = Math.floor(boffset/(bpp+headerLength));
    return npkt*bpp;
  }

  @Override
  public void open (DataFile df) {
    setFormat(df.getFormat());
    if (df.hp!=null) df.hp.open();
  }

  @Override
  public void seek (DataFile df, double boffset) {
    boffset = dataToPacketOffset (df,boffset);
    if (df.hp!=null) df.hp.seek(npktoff);
    if (df.io!=null) df.io.seek((long)(df.getDataStart()+boffset));
  }

  @Override
  public int read (DataFile df, byte[] buf, int boff, int bytes, long lbuf) {
    if (ipass==0) nhdrs=0; ipass++;
    if (ipoff==0) {             // read packet header
      if (nhdrs==mhdrs) setMaxHeaders(mhdrs*2);
      if (df.hp!=null) df.hp.read (this.buf,nhdrs*headerLength,headerLength);
      else df.io.read (this.buf,nhdrs*headerLength,headerLength);
      nhdrs++;
    }
    int nbp = getBytes();
    int nb = Math.min(bytes,nbp-ipoff);
    if (buf!=null) nb = df.io.read(buf,boff,nb);
    else           nb = df.io.read(lbuf,boff,nb);
    if (nb<0) return nb;
    ipoff+=nb; if (ipoff==nbp) ipoff=0;                      // signal next packet header
    if (bytes>nb) nb += read(df,buf,boff+nb,bytes-nb,lbuf);  // pick up the rest
    ipass--;
    return nb;
  }

  @Override
  public int write (DataFile df, byte[] buf, int boff, int bytes, long lbuf) {
    if (ipass==0) ihdrs=0; ipass++;
    if (ipoff==0) {              // write packet header
      if (df.hp!=null) df.hp.write (this.buf,ihdrs*headerLength,headerLength);
      else df.io.write (this.buf,ihdrs*headerLength,headerLength);
      ihdrs++;
    }
    int nbp = getBytes();
    int nb = Math.min(bytes,nbp-ipoff);
    if (buf!=null) nb = df.io.write(buf,boff,nb);
    else           nb = df.io.write(lbuf,boff,nb);
    if (nb<0) return nb;
    ipoff+=nb; if (ipoff==nbp) ipoff=0;                         // signal next packet header
    if (bytes>nb) nb += write(df,buf,boff+nb,bytes-nb,lbuf);    // pick up the rest
    ipass--;
    return nb;
  }

  @Override
  public void close (DataFile df) {
    if (df.hp!=null) df.hp.close();
  }

  @Override
  public boolean hasHeader () {
    return nhdrs>0;
  }

  @Override
  public String listHeader () {
    String list = "ICE-Packet Count="+getUnsignedCount()+" Elem="+getSize()+
      " Chan="+getChannel()+" Form="+getFormat()+" TC="+getTC()+" at "+getTCO();
    return list;
  }

  @Override
  public PacketHandler cloneOf () {
    PacketHandler ph=null;
    try { ph = (PacketHandler)this.clone(); }
    catch (Exception e) {}
    return ph;
  }

  /** Copy from another ICE packet object
   *  @param source source ICE packet object */
  public void copyFrom (ICEPacket source) {
    setBytes(source.buf,0);
  }

  /** Set the maximum number of headers (should to be 1 or more). */
  private void setMaxHeaders (int nmhdrs) {
    byte[] nbuf = new byte[nmhdrs*headerLength];
    if (nhdrs>0) System.arraycopy(buf,0, nbuf,0, nhdrs*headerLength);
    this.buf = nbuf;
    this.mhdrs = nmhdrs;
  }

  /** set ICE packet from the specified byte array
   *  @param ibuf   byte array to use in setting ICE Packet
   *  @param offset starting offset into byt array
   */
  public void setBuffer (byte[] ibuf,int offset) {
    System.arraycopy(ibuf,offset,buf,0,headerLength);
  }

  @Override
  public String toString() {
    String iceHeaderInfo = "ICE Header "+getUnsignedCount()+" SYNC: "+hasSync()+" User key: "+getUserKey()+"\n"+
                           "   Size (elem): "+getSize()+
                           "   User: "+getUser()+" Channel: "+getChannel()+"\n"+
                           "   Rep: "+getRep()+" BPA: "+getBPA()+"\n"+
                           "   Format: "+getFormat()+
                           "   Timecode: "+getTC()+"\n"+
                           "   Data 1 / XDELTA: "+getUserDataD(ICEPacket.XDELTA_POSITION_IN_USER_DATA)+
                             " Data 2 / XSTART: "+getUserDataD(ICEPacket.XSTART_POSITION_IN_USER_DATA)+"\n";
    return iceHeaderInfo;
  }

  /** Convert the byte representation into its String equivalent
   *  @param rep representation in byte form
   *  @return representation in String form
   */
  private String getRepString (byte rep) {
    String stringRep = "";
    switch ((char)rep) {
      case 'I':
        stringRep = "IEEE";
        break;
      case 'E':
        stringRep = "EEEI";
        break;
      case 'V':
        stringRep = "VAX";
        break;
    }
    return stringRep;
  }

}
