package nxm.sys.lib;

import nxm.sys.inc.PacketHandler;

/**
  Defines the header for SDDS packetized data.

  <pre>
    07:00 ICE/UDP header
    09:08 Format Identifier  (bits,mode)
    11:10 Frame  Sequence
    12:12 TimeTag Status
    13:12 TimeTag 1ms pointer
    15:14 TimeTag 1ms delta
    23:16 TimeTag 250ps tics (normal precision)
    27:24 TimeTag 250ps/2**32 tics (extended precision)
    39:28 SSC Info
    43:40 SSD Info
    63:44 AAD Info
  </pre>

  TimeTag Status byte
    0x80  1ms PTR valid
    0x40  Time Tag valid
    0x20  SSC Valid


  Standard Format Identifiers are:
    16 bit    FID=0x1082
    8  bit    FID=0x0881
    4  bit    FID=0x0480

  @author Jeff Schoen

*/
public class SDDSPacket implements PacketHandler,Cloneable {

  /** flag for determing UDP header */
  public static final int UDP=0x1;
  /** flag for determining ICE header */
  public static final int ICE=0x2;
  /** (1 tic / 250-picoseconds) = (1 tic / 250e-12 sec) = (1 tic / 25e-9 sec) = 4e9 tics/second */
  private static final long ticsPerSecond = 4000000000L;
  private static final long ticsPerMilliSecond = 4000000L;
  private static final double secondsPerYear = 365 * 86400;
  private int headerOffset=8, headerLength=56, dataLength=1024, totalLength=1080;
  private int size, bytes, count, fixedSize=1024, bpa=1, ipoff, missed, flags;
  private boolean header=false, udp=false, ice=false, tc1ms=true;
  private byte[] buf = new byte[64];
  private double npktoff;
  private double yearInSeconds;
  private final byte TCS_1MSV=-128, TCS_TTV=0x40, TCS_SSC=0x20;

  /** Default Constructor */
  public SDDSPacket() {
    this("SB",0);
  }

  /** Constructor
      @param format Midas format digraph
      @param flags determines UDP vs ICE header
   */
  public SDDSPacket (String format, int flags) {
    this.flags = flags;
    setCount(0);
    setFormat(format);
    setTC(0,0.0,0.0,0.0);
    if ((flags&ICE)!=0) setICE(true);
    else if ((flags&UDP)!=0) setUDP(true);
    setKeys(0xAA55);
    setPort(0);
    setAddress(0);
  }

  /** set UDP packet header mode
      @param state UDP packet header mode */
  public void setUDP (boolean state) {
    udp = state;
    if (!udp) ice=false;
    if (udp) { headerOffset=0; headerLength=64; }
    else { headerOffset=8; headerLength=56; }
    totalLength = headerLength+dataLength;
  }

  /** set ICE/UDP packet header mode
      @param state ICE/UDP packet header mode */
  public void setICE (boolean state) {
    ice = state;
    setUDP(state);
  }

  /** sets IP address in UDP header extension
      @param addr IP address
   */
  public void setAddress (int addr) {
    buf[4]=(byte)((addr>>24)&0xFF);
    buf[5]=(byte)((addr>>16)&0xFF);
    buf[6]=(byte)((addr>> 8)&0xFF);
    buf[7]=(byte)((addr>> 0)&0xFF);
  }
  /** gets the current IP address
      @return IP address
   */
  public int getAddress() {
    return ((buf[4]<<24) | (buf[5]<<16) | (buf[6]<<8) | buf[7]);
  }
  /** gets the String representation of the current IP address
      @return String representation of IP address
   */
  public String getAddrStr() {
    return getAddrStr(buf[4])+"."+getAddrStr(buf[5])+"."+getAddrStr(buf[6])+"."+getAddrStr(buf[7]);
  }
  private String getAddrStr (byte b) {
    int i = b; i &= 0xFF; return ""+i;
  }

  /** sets UDP port number in UDP header extension
      @param port UDP port number*/
  public void setPort (int port) {
    buf[2]=(byte)((port>> 8)&0xFF);
    buf[3]=(byte)((port>> 0)&0xFF);
  }
  /** Get the port
   *  @return port */
  public int getPort() {
    return (((buf[2]<<8)&0xFF00) | (buf[3]&0xFF));
  }

  /** sets specified pattern in 1st 2 bytes of UDP header extension
      @param key pattern in 1st 2 bytes of UDP header extension*/
  public void setKeys (int key) {
    buf[0]=(byte)((key>> 8)&0xFF);
    buf[1]=(byte)((key>> 0)&0xFF);
  }

  /** set number of bits per sample
      @param bits number of bits per sample */
  public void setBits (int bits) {
    this.bpa = bits/8;
    buf[8] = (byte)0x80;		// Standard Format
    buf[9] = (byte)bits;		// bits per sample
         if (bits<=4) buf[8]|=0;	// format mode 4b
    else if (bits<=8) buf[8]|=1;	// format mode 8b
    else if (bits<=16) buf[8]|=2;	// format mode 16b
  }

  /** Get the bits per sample
   *  @return bits per sample */
  public int getBits() {
    return 0x1F&(int)buf[9];
  }

  /** packet number since start of xfer
      @param count packet number since start of xfer*/
  public void setCount (int count) {
    count = count&0xFFFF;
    this.count = count;
    buf[10] = (byte)((count>>8)&0xFF);
    buf[11] = (byte)((count)&0xFF);
  }

  /** up the count by one */
  public void upCount() {
    if (((++count)&0x1F)==0x1F) count++;
    setCount(count);
  }

  /** set format/bits from Midas format digraph
      @param format Midas format digraph */
  public void setFormat (String format) {
    if (format.equals("SI")) setBits(16);
    else if (format.equals("SN")) setBits(4);
    else setBits(8);
  }

  /** set timecode
   *  @param offset unused parameter
   *  @param delta time delta
   *  @param wsec  whole seconds
   *  @param fsec  fractional seconds
   */
  public void setTC (int offset, double delta, double wsec, double fsec) {
    if (wsec>secondsPerYear) {
      if (yearInSeconds==0) yearInSeconds = (new Time(wsec,fsec)).getYiS();
      wsec -= yearInSeconds;
    }
    long tics = (long)(wsec*ticsPerSecond) + (long)(fsec*ticsPerSecond);
    Convert.packX(buf,16,tics,Convert.IEEE); // Bug 1893 Data should be in network byte order (IEEE)
    int mask = 0x00;
    if (delta>0) {
      mask |= TCS_TTV;
      if (tc1ms) {
        int msec = (int)(fsec*1000)+1;
        int mtic = (int)((msec*.001-fsec)/delta);
        if (mtic*bpa<1024) {  // 1ms tic in packet
          mask |= TCS_1MSV;
        }
      }
    }
    buf[12] = (byte)mask;
  }

  /** Get timecode
   *  @return timecode */
  public Time getTC() {
    return getTC(buf,0,0L);
  }

  /** get timecode from ICE/SDDS formatted header starting at off in buf[] or native lbuf
   *  @param buf  buffer containing ICE/SDDS formatted header
   *  @param off  starting offset
   *  @param lbuf native pointer
   *  @return timecode
   */
  public static Time getTC (byte[] buf, int off, long lbuf) {
    long tics = 0;
    if (buf!=null) {
      if ((buf[12]&0xC0)==0) return null; // no timecode - moved this check inside null check in 3.9.1
      tics = Convert.unpackX(buf, off+16, Convert.IEEE); // Bug 1893 data is in network byte order = IEEE
    }
    else if (lbuf!=0) {
      byte[] tcbuf = new byte[8];
      Native.p2ja(lbuf,off+16, tcbuf,0, 8);
      tics = Convert.unpackX(tcbuf, 0, Convert.IEEE);    // Bug 1893 data is in network byte order = IEEE
    }
    long wsec = tics / ticsPerSecond;
    long fsec = tics - (wsec*ticsPerSecond);
    return new Time ((double)wsec,((double)fsec)*250e-12);
  }

  /** Get timecode offset
   *  @return timecode offset */
  public int getTCO() {
    if ((buf[12]&0x80)==0) return -1; // no valid 1ms
    int off = ((buf[12]<<8)&0x0700) | (buf[13]&0xFF);
    return off;
  }

  /** Get the current packet count
   *  @return the current packet count */
  public int getCount() {
    int count = ((buf[10]<<8)&0xFF00) | (buf[11]&0xFF);
    return count;
  }

  /** Get the current Format
   *  @return the current Format */
  public String getFormat() {
    if (bpa==2) return "SI";
    else return "SB";
  }

  /** Get size of packet data in elements
   *  @return Size of packet data in elements */
  public int getSize() {
    if (bpa==2) return fixedSize>>1;
    else return fixedSize;
  }

  /** Get size of packet data in bytes
   *  @return Size of packet data in bytes */
  public int getBytes() {
    return fixedSize;
  }

  /** check count for dropped packets */
  private void updateCount() {
    int ncount = getCount();
    if (count!=0) {
      if (((++count)&0x1F)==0x1F) count++;
      if (ncount>count) missed += (ncount-count);
    }
    count = ncount;
  }

  @Override
  public String getConfiguration (DataFile df) {
    String config = "SDDS";
    if (ice) config += "/ICE";
    else if (udp) config += "/UDP";
    if (df.hp!=null) {
      config += "/DET";
      String aux = df.hp.getAux();
      if (aux != null && aux.length() > 0) config += "=" + aux;
    }
    return config;
  }

  /** {@inheritDoc} */
  @Override
  public void setFileName (DataFile df, FileName filename) {
    DataFile hp = new DataFile();
    hp.init(null,filename,"3000","NH",df.getPacketHandlerFlags());
    String srec="FORM|SB,BPS|SB,FRAM|SI,SOFF|SI,OTIC|SI,YTIC|CL,PTIC|SL,SSC|6I,SSD|4B,AAD|5L";
    if (udp) srec = "KEY|2B,PORT|SI,ADDR|4B,"+srec;
    hp.setSubRecords(srec);
    hp.setDataRep("IEEE");
    df.hp=hp;
  }

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

  /** {@inheritDoc} */
  @Override
  public double dataToPacketOffset (DataFile df, double boffset) {
    int bpp = fixedSize;
    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;
  }

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

  /** {@inheritDoc} */
  @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));
  }

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

  /** {@inheritDoc} */
  @Override
  public int read (DataFile df, byte[] buf, int boff, int bytes, long lbuf) {
    header = (ipoff==0);
    if (!header);  							// skip packet header
    else if (df.hp!=null) df.hp.read (this.buf,headerOffset,headerLength); // read packet header
    else df.io.read (this.buf,headerOffset,headerLength); 		// read packet header
    if (header) updateCount();
    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
    return nb;
  }

  /** {@inheritDoc} */
  @Override
  public int write (DataFile df, byte[] buf, int boff, int bytes, long lbuf) {
    header = (ipoff==0);
    if (!header);  							// skip packet header
    else if (df.hp!=null) df.hp.write (this.buf,headerOffset,headerLength); // write packet header
    else df.io.write (this.buf,headerOffset,headerLength); 		// write packet header
    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
    return nb;
  }

  /** {@inheritDoc} */
  @Override
  public void close (DataFile df) {
    if (df.hp!=null) df.hp.close();
    if (missed>0) Shell.warning("Packet sequence inconsistency, missed "+missed+" packets");
  }

  /** {@inheritDoc} */
  @Override
  public boolean hasHeader() {
    return header;
  }

  /** {@inheritDoc} */
  @Override
  public String listHeader() {
    Time t = getTC();
    String ts = (t!=null)? t.toString(12) : null;
    String list = "SDDS-Packet Addr="+getAddrStr()+" Port="+getPort()+" Count="+getCount()+
      " Elem="+getSize()+" Bits="+getBits()+" TC="+ts+" 1msOff="+getTCO();
    return list;
  }

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

  /** copy bytes from a user buffer into the 64by header buffer
   * @param buffer user buffer
   * @param boff   64by header buffer
   */
  public void setBuffer (byte[] buffer, int boff) {
    System.arraycopy(buffer,boff, buf,0, 64);
  }

  /** copy bytes from the 64by header buffer into a user buffer
   *  @param buffer 64by header buffer
   *  @param boff   user buffer
   */
  public void getBuffer (byte[] buffer, int boff) {
    System.arraycopy(buf,0, buffer,boff, 64);
  }

  /** Get the buffer
   *  @return the buffer*/
  public byte[] getBuffer () {
    return buf;
  }

}
