package nxm.audio.prim;

import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.Primitive;

/**
* Mulaw decoder. Decodes Mu-law encoded file to linear (SI).
* <pre>
* [input]  Mu-law encoded SB input
* [output] Linear output
* [time]   Optional timecode output file
*
* Decodes Mu-law encoded file to linear (SI). Generates a mapping
* table, then decodes via table lookup. if [time] specified, time is
* output.
* </pre>
*
* @author X-Midas 4.14.0
* @author Juan Velasquez
* @since 3.7.1
*/

public class mulawdecode extends Primitive {

  private DataFile hin;
  private DataFile hout;
  private DataFile htim;
  private byte[] btable;
  private short[] itable;
  private Data vbbuf;
  private Data vibuf;
  private Data tbuf;
  private boolean byteout = false;

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

  @Override
  public int open() {
    hin = MA.getDataFile("IN", "1000,2000", "SB", DataFile.INPUT);
    hin.open();
    if (!hin.isOpen()) return FINISH;
    hin.setFrameSize(0);

    hout = MA.getDataFile("OUT", hin, DataFile.OUTPUT|DataFile.OPTIONAL);
    hout.setType(1000);
    // NOTE: The Original XMidas mulaw decode never sets the byteout variable.
    // byteout is a private variable that it internally initialized to be false.
    if (!byteout) {
      hout.setFormat("SI");
    }
    hout.open();

    htim = MA.getDataFile("TIME", "1000", "SD", DataFile.OUTPUT|DataFile.OPTIONAL);
    htim.open();

    // bufSize is the default buffer size defined on nxm.sys.lib.Primitive class
    xfer = Math.max(hin.bpa, bufSize);
    xfer = MA.getL("/TL", xfer);

    hin.setXferLength(xfer);

    vbbuf = hin.getDataBuffer(xfer);
    vibuf = hout.getDataBuffer(xfer);

    tbuf = (htim.isOpen())? htim.getDataBuffer(xfer) : null;

    if (byteout) { btable = mulawTableByteInit(); }
    else { itable = mulawTableShortInit(); }

    return NORMAL;
  }

  @Override
  public int process() {
    int numRead = hin.read(vbbuf);
    if (numRead < 0) { return FINISH; }
    if (numRead == 0) { return NOOP; }

    switch(hin.getFormatType()) {
      case 'I':
        decodeShort(numRead);
        break;
      case 'B':
        decodeByte(numRead);
        break;
      default:
        throw new AssertionError("Buffer type not supported yet");
    }
    return NORMAL;
  }

  /**
   * void function for decoding short data buffer
   * @param numRead
   */
  private void decodeShort(int numRead) {
    double time = 0.0d;
    double nel = 0.0d;

    byte[] bbuf = vbbuf.castB(true);
    short[] ibuf = vibuf.castI(false);
    if (htim.isOpen()) {
      time = hin.getXStart() + nel * hin.getXDelta();
      tbuf.setD(0, time);
      htim.write(tbuf, 1);
    }

    if (byteout) {
      for (int i=0; i<numRead; ++i) {
        ibuf[i] = btable[(bbuf[i]+128)];
      }
    } else {
      for (int i=0; i<numRead; ++i) {
        ibuf[i] = itable[(bbuf[i]+128)];
      }
    }
    vbbuf.uncast(bbuf, false);
    vibuf.uncast(ibuf, true);
    hout.write(vibuf, numRead);
  }

  /**
   * void function ofr decoding a byte buffer
   * @param numRead
   */
  private void decodeByte(int numRead) {
    double time =0.0d;
    double nel = 0.0d;

    byte[] bbuf = vbbuf.castB(true);
    byte[] ibuf = vibuf.castB(false);
    if (htim.isOpen()) {
      time = hin.getXStart() + nel * hin.getXDelta();
      tbuf.setD(0, time);
      htim.write(tbuf, 1);
    }

    if (byteout) {
      for (int i=0; i<numRead; ++i) {
        ibuf[i] = btable[(bbuf[i]+128)];
      }
    } else {
      for (int i=0; i<numRead; ++i) {
        ibuf[i] =  (byte) itable[(bbuf[i]+128)];
      }
    }
    vbbuf.uncast(bbuf, false);
    vibuf.uncast(ibuf, true);
    hout.write(vibuf, numRead);
  }

  /**
   * Close all open DataFiles
   */
  @Override
  public int close() {
    if (hin.isOpen()) { hin.close(); }
    if (hout.isOpen()) { hout.close(); }
    if (htim.isOpen()) { htim.close(); }
    return NORMAL;
  }

  /**
   * Private method for creating a decoding mulaw table lookup
   * @return
   */
  private byte[] mulawTableByteInit() {
    byte[] btable = new byte[256];
    byte btemp;

    for (int i=-128; i<=126; ++i) {
      btemp = (byte) i;
      if (btemp >= 0) {
        btemp = (byte) ((btemp ^ (byte)0x80) + 1);
      }
      else {
        btemp = (byte) (btemp ^ (byte)0xFF);
      }
      btable[i+128] = (byte) Math.round(((Math.pow(256.0, (Math.abs(btemp/127.0)))-1.0)*127.0)/255.0); // fixed in 3.9.2 for NXM-3428

      if (btemp < 0) {
        btable[i+128] = (byte) -btable[i+128];
      }
    }
    return btable;
  }

  /**
   * The decoded mulaw.
   *
   * @return short table containing 16 bit
   */
  private short[] mulawTableShortInit() {
    short[] itable = new short[256];

    byte btemp;
    for (int i=-128; i<=126; i++) {
      btemp = (byte) i;

      // check for the sign of the byte.
      // positive or negative.
      if (btemp >= 0) {
        btemp =   (byte) ((btemp ^ (byte)0x80) + 1); // negate sign bit and add one
      }
      else {
        btemp =  (byte) (btemp ^ (byte)0XFF); // negate all bits
      }

      // compute the decode mulaw value
      itable[i+128] = (short) Math.round(((Math.pow(256.0, (Math.abs(btemp/127.0)))-1.0)*32767.0)/255.0);

      if (btemp < 0) {
        itable[i+128] = (short) -itable[i+128];
      }
    }
    return itable;
  }
}
