package nxm.audio.prim;

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

/**
* Mulaw Encoder. Accepts linear audio file and converts to 8 bit mu-law.
* <pre>
* [infile] Linear audio input file
* [outfile] 8 bit mu-law output file
*
* Input can be SI, SL, SF or SF type 1000 or type-2000.
* Output is SB; type follows input. to avoid distortion, user
* should scale input data such that no sample exceeds +/-32767.
*
* Switches: /TL=n Transfer length (minimum of 32k used).
* </pre>
*
* @author X-Midas 4.14.0
* @author Juan Velasquez
* @since 3.7.1
*/

public class mulawencode extends Primitive {

  private DataFile hin;
  private DataFile hout;
  private Data fdata;
  private Data bdata;
  private byte[] mulaw_table;

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

  @Override
  public int open() {

    hin = MA.getDataFile("IN", "1000,2000", "S#,-#P",DataFile.INPUT);
    hin.open();
    if (!hin.isOpen()) return FINISH;

    hout = MA.getDataFile("OUT", hin, DataFile.OUTPUT);
    hout.setFormat("SB");
    hout.open();
    if (!hout.isOpen()) return FINISH;

    hin.setFrameSize(0);
    hout.setFrameSize(0);

    xfer = Math.max(hin.bpa, bufSize);
    xfer = MA.getL("/TL", xfer );

    if (hin.typeClass == 2) {
      xfer = hin.getSubSize();
    }

    fdata = hin.getDataBuffer(xfer);
    bdata = hin.getDataBuffer(xfer);

    // create mulaw Table before process
    mulaw_table = createMulawTable();

    return NORMAL;
  }

  @Override
  public int process() {
    // Read input buffer, returning max number of bytes read
    int numRead = hin.read(fdata);
    if (numRead < 0) { return FINISH; }
    if (numRead == 0) { return NOOP; }

    // encode according to the type of data received as input file
    switch(hin.getFormatType()) {
      case DataTypes.FLOAT:
        encodeFloat(numRead);
        break;
      case DataTypes.DOUBLE:
        encodeDouble(numRead);
        break;
      case DataTypes.LONG:
        encodeInt(numRead);
        break;
      case DataTypes.INT:
        encodeShort(numRead);
        break;
      case DataTypes.XLONG:
        encodeLong(numRead);
        break;
      case DataTypes.BYTE:
        encodeByte(numRead);
        break;
      default:
        M.info("Buffer type not supported yet Type: "+hin.getFormatType());
        break;
    }
    return NORMAL;
  }

  /**
   * void method for encoding floating values.
   * @param numRead
   */
  private void encodeFloat(int numRead) {
    int val = 0;
    // If the NeXtMidas file is a type 2000
    if (hin.typeClass == 2 && numRead < xfer) {
      float[] buf = fdata.castF(true);
      float[] ibuf = bdata.castF(false);
      // Pad incomplete frames with zeros
      for (int i = numRead + 1; i < xfer; ++i) {
        buf[i] = 0.0f;
      }

      for (int i=0; i<xfer; ++i) {
        val = Math.min(10000, (int)(Math.round(0.25 * buf[i])));
        if (val < 0) {
          val = Math.abs(val);
        }
        else if (val > 0) {
          val = 5000 + val;
        }
        ibuf[i] = mulaw_table[Math.max(0, val)];
      }

      fdata.uncast(buf, false);
      bdata.uncast(ibuf, true);
      hout.write(bdata, xfer);
    }
    else {
      float[] buf = fdata.castF(true);
      float[] ibuf = bdata.castF(false);
      for (int i=0; i < numRead; ++i) {
        val = Math.min(10000, (int)(Math.round(0.25 * buf[i])));
        if (val < 0) {
          val = Math.abs(val);
        }
        else if (val > 0) {
          val = 5000 + val;
        }
        ibuf[i] = mulaw_table[Math.max(0, val)];
      }
      fdata.uncast(buf, false);
      bdata.uncast(ibuf, true);
      hout.write(bdata, numRead);
    }
  }

  /**
   * Encode for a double data buffer
   * @param numRead
   */
  private void encodeDouble(int numRead) {
    int val = 0;
    if (hin.typeClass == 2 && numRead < xfer) {
      double[] buf = fdata.castD(true);
      double[] ibuf = bdata.castD(false);
      // Pad incomplete frames with zeros
      for (int i = numRead + 1; i < xfer; ++i) {
        buf[i] = 0.0;
      }

      for (int i=0; i<xfer; ++i) {
        val = Math.min(10000, (int)(Math.round(0.25 * buf[i])));
        if (val < 0) {
          val = Math.abs(val);
        }
        else if (val > 0) {
          val = 5000 + val;
        }
        ibuf[i] = mulaw_table[Math.max(0, val)];
      }

      fdata.uncast(buf, false);
      bdata.uncast(ibuf, true);
      hout.write(bdata, xfer);
    }
    else {
      double[] buf = fdata.castD(true);
      double[] ibuf = bdata.castD(false);
      for (int i=0; i < numRead; ++i) {
        val = Math.min(10000, (int)(Math.round(0.25 * buf[i])));
        if (val < 0) {
          val = Math.abs(val);
        }
        else if (val > 0) {
          val = 5000 + val;
        }
        ibuf[i] = mulaw_table[Math.max(0, val)];
      }

      fdata.uncast(buf, false);
      bdata.uncast(ibuf, true);
      hout.write(bdata, numRead);
    }
  }

  /**
   * Encode for an integer data buffer
   * @param numRead
   */
  private void encodeInt(int numRead) {
    int val = 0;
     if (hin.typeClass == 2 && numRead < xfer) {
      int[] buf = fdata.castL(true);
      int[] ibuf = bdata.castL(false);
      // Pad incomplete frames with zeros
      for (int i = numRead + 1; i < xfer; ++i) {
        buf[i] = (int) 0.0;
      }

      for (int i=0; i<xfer; ++i) {
        val = Math.min(10000, (int)(Math.round(0.25 * buf[i])));
        if (val < 0) {
          val = Math.abs(val);
        }
        else if (val > 0) {
          val = 5000 + val;
        }

        ibuf[i] = mulaw_table[Math.max(0, val)];
      }

      fdata.uncast(buf, false);
      bdata.uncast(ibuf, true);
      hout.write(bdata, xfer);
    }
    else {
      int[] buf = fdata.castL(true);
      int[] ibuf = bdata.castL(false);
      for (int i=0; i < numRead; ++i) {
        val = Math.min(10000, (int)(Math.round(0.25 * buf[i])));
        if (val < 0) {
          val = Math.abs(val);
        }
        else if (val > 0) {
          val = 5000 + val;
        }
        ibuf[i] = mulaw_table[Math.max(0, val)];
      }

      fdata.uncast(buf, false);
      bdata.uncast(ibuf, true);
      hout.write(bdata, numRead);
    }
  }

  /**
   * Encode for a short data buffer
   * @param numRead
   */
  private void encodeShort(int numRead) {
    int val = 0;
    if (hin.typeClass == 2 && numRead < xfer) {
      short[] buf = fdata.castI(true);
      short[] ibuf = bdata.castI(false);
      // Pad incomplete frames with zeros
      for (int i = numRead + 1; i < xfer; ++i) {
        buf[i] = (short) 0.0;
      }

      for (int i=0; i<xfer; ++i) {
        val = Math.min(10000, (int)(Math.round(0.25 * buf[i])));
        if (val < 0) {
          val = Math.abs(val);
        }
        else if (val > 0) {
          val = 5000 + val;
        }
        ibuf[i] = mulaw_table[Math.max(0, val)];
      }

      fdata.uncast(buf, false);
      bdata.uncast(ibuf, true);
      hout.write(bdata, xfer);
    }
    else {
      short[] buf = fdata.castI(true);
      short[] ibuf = bdata.castI(false);
      for (int i=0; i < numRead; ++i) {
        val = Math.min(10000, (int)(Math.round(0.25 * buf[i])));
        if (val < 0) {
          val = Math.abs(val);
        }
        else if (val > 0) {
          val = 5000 + val;
        }
        ibuf[i] = mulaw_table[Math.max(0, val)];
      }

      fdata.uncast(buf, false);
      bdata.uncast(ibuf, true);
      hout.write(bdata, numRead);
    }
  }

  /**
   * Encode for a long data buffer
   * @param numRead
   */
  private void encodeLong(int numRead) {
    int val = 0;
    if (hin.typeClass == 2 && numRead < xfer) {
      long[] buf = fdata.castX(true);
      long[] ibuf = bdata.castX(false);
      // Pad incomplete frames with zeros
      for (int i = numRead + 1; i < xfer; ++i) {
        buf[i] = (long) 0.0;
      }

      for (int i=0; i<xfer; ++i) {
        val = Math.min(10000, (int)(Math.round(0.25 * buf[i])));
        if (val < 0) {
          val = Math.abs(val);
        }
        else if (val > 0) {
          val = 5000 + val;
        }
        ibuf[i] = mulaw_table[Math.max(0, val)];
      }

      fdata.uncast(buf, false);
      bdata.uncast(ibuf, true);
      hout.write(bdata, xfer);
    }
    else {
      long[] buf = fdata.castX(true);
      long[] ibuf = bdata.castX(false);
      for (int i=0; i < numRead; ++i) {
        val = Math.min(10000, (int)(Math.round(0.25 * buf[i])));
        if (val < 0) {
          val = Math.abs(val);
        }
        else if (val > 0) {
          val = 5000 + val;
        }
        ibuf[i] = mulaw_table[Math.max(0, val)];
      }
      fdata.uncast(buf, false);
      bdata.uncast(ibuf, true);
      hout.write(bdata, numRead);
    }
  }

  /**
   * Encode for a byte byte buffer
   * @param numRead
   */
  private void encodeByte(int numRead) {
    int val = 0;
    if (hin.typeClass == 2 && numRead < xfer) {
      byte[] buf = fdata.castB(true);
      byte[] ibuf = bdata.castB(false);
      // Pad incomplete frames with zeros
      for (int i = numRead + 1; i < xfer; ++i) {
        buf[i] = (byte) 0.0;
      }

      for (int i=0; i<xfer; ++i) {
        val = Math.min(10000, (int)(Math.round(0.25 * buf[i])));
        if (val < 0) {
          val = Math.abs(val);
        }
        else if (val > 0) {
          val = 5000 + val;
        }
        ibuf[i] = mulaw_table[Math.max(0, val)];
      }
      fdata.uncast(buf, false);
      bdata.uncast(ibuf, true);
      hout.write(bdata, xfer);
    }
    else {
      byte[] buf = fdata.castB(true);
      byte[] ibuf = bdata.castB(false);
      for (int i=0; i < numRead; ++i) {
        val = Math.min(10000, (int)(Math.round(0.25 * buf[i])));
        if (val < 0) {
          val = Math.abs(val);
        }
        else if (val > 0) {
          val = 5000 + val;
        }
        ibuf[i] = mulaw_table[Math.max(0, val)];
      }
      fdata.uncast(buf, false);
      bdata.uncast(ibuf, true);
      hout.write(bdata, numRead);
    }
  }

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


  /**
   * Method for creating a Mulaw Table lookup
   * This table will encode a Mulaw byte from 16-bit signed integer
   * @return byte array of mulaw encode values
   */
  private byte[] createMulawTable() {
    // we create a data buffer to contain 10000 bytes
    byte[] mulaw_table = new byte[10001];
    float factor1 = (float) (127.0 / Math.log(256.0));
    float factor2 = (float) (255.0 / 5000.0);

    // creating a mulaw table from -127 <= 0 >= 127 (-5000 <= 0 >= 5000 buffer size)
    for(int i=0; i<=5000; ++i) {
      mulaw_table[i + 5000] = (byte)(Math.round(factor1 * Math.log(1.0 + factor2 * (i))));
      mulaw_table[i] =  (byte) -mulaw_table[i + 5000];
    }

    for (int i=10000; i>=0; --i) {
      if (mulaw_table[i] < 0) {
        mulaw_table[i] = (byte) (mulaw_table[i] - 1); // subtract one
        mulaw_table[i] =  (byte) (mulaw_table[i] ^ (byte)0x80); // negate sign bit
      } else { mulaw_table[i] = (byte) (mulaw_table[i] ^ (byte)0xFF); } // negate all bits
    }
    return mulaw_table;
  }
}
