/*
 * Jitsi Videobridge, OpenSource video conferencing.
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */

package org.jitsi.videobridge.openfire;

import org.slf4j.*;
import org.slf4j.Logger;

public class AudioConversion
{
    private static final Logger Log = LoggerFactory.getLogger(AudioConversion.class);
    public static final int PCMU_SILENCE   = 0x7f;
    public static final int PCM_SILENCE = 0x0;

    /*
     * table for converting from linear to ulaw
     */
    static private byte[] linearToUlawTable;

    static {
	long start = System.currentTimeMillis();

    	int BIAS = 0x84;   // add-in bias for 16 bit samples
    	int CLIP = 32635;

        int exp_lut[] = {
    	    0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
            4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
            5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
            5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
            6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
            6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
            6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
            6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
            7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
            7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
            7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
            7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
            7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
            7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
            7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
            7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
        };

	linearToUlawTable = new byte[65536];

	for (int i = 0; i < 65536; i++) {
            int sign, exponent, mantissa;
            byte ulawbyte;

	    short sample = (short) i;

            /* get the sample into sign-magnitude */
            sign = (sample >> 8) & 0x80;        // set aside the sign

            if (sign != 0) {
	        sample = (short)-sample;        // get magnitude
	    }

            if (sample > CLIP) {
	        sample = (short) CLIP;   	// clip the magnitude
	    }

            /* convert from 16 bit linear to ulaw */
            sample = (short)(sample + BIAS);
            exponent = exp_lut[(sample>>7) & 0xFF];
            mantissa = (sample >> (exponent+3)) & 0x0F;
            ulawbyte = (byte)(~(sign | (exponent << 4) | mantissa));

            if (ulawbyte == 0) {
    	        ulawbyte = 0x02;  	    // optional CCITT trap
	    }

	    /*
	     * For debugging to match files sent and received
	     */
	    //if (ulawbyte == 0xff) {
	    //    ulawbyte = 0x7f;
	    //}

	    linearToUlawTable[i] = ulawbyte;
	}

	Log.debug("Time to generate l2u table " + (System.currentTimeMillis() - start));
    }

    /*
     * Index = ulaw value + 128, entry = signed 16 bit linear value.
     */
    static final private int[] ulawToLinearTable = {
 	 32635,  31608,  30584,  29560,  28536,  27512,  26488,  25464,
	 24440,  23416,  22392,  21368,  20344,  19320,  18296,  17272,
	 16248,  15736,  15224,  14712,  14200,  13688,  13176,  12664,
	 12152,  11640,  11128,  10616,  10104,   9592,   9080,   8568,
	  8056,   7800,   7544,   7288,   7032,   6776,   6520,   6264,
	  6008,   5752,   5496,   5240,   4984,   4728,   4472,   4216,
          3960,   3832,   3704,   3576,   3448,   3320,   3192,   3064,
          2936,   2808,   2680,   2552,   2424,   2296,   2168,   2040,
	  1912,   1848,   1784,   1720,   1656,   1592,   1528,   1464,
          1400,   1336,   1272,   1208,   1144,   1080,   1016,    952,
           888,    856,    824,    792,    760,    728,    696,    664,
	   632,    600,    568,    536,    504,    472,    440,    408,
	   376,    360,    344,    328,    312,    296,    280,    264,
	   248,    232,    216,    200,    184,    168,    152,    136,
	   120,    112,    104,     96,     88,     80,     72,     64,
	    56,     48,     40,     32,     24,     16,      8,      0,
        -32760, -31608, -30584, -29560, -28536, -27512, -26488, -25464,
	-24440, -23416, -22392, -21368, -20344, -19320, -18296, -17272,
	-16248, -15736, -15224, -14712, -14200, -13688, -13176, -12664,
	-12152, -11640, -11128, -10616, -10104,  -9592,  -9080,  -8568,
	 -8056,  -7800,  -7544,  -7288,  -7032,  -6776,  -6520,  -6264,
	 -6008,  -5752,  -5496,  -5240,  -4984,  -4728,  -4472,  -4216,
	 -3960,  -3832,  -3704,  -3576,  -3448,  -3320,  -3192,  -3064,
	 -2936,  -2808,  -2680,  -2552,  -2424,  -2296,  -2168,  -2040,
         -1912,  -1848,  -1784,  -1720,  -1656,  -1592,  -1528,  -1464,
	 -1400,  -1336,  -1272,  -1208,  -1144,  -1080,  -1016,   -952,
	  -888,   -856,   -824,   -792,   -760,   -728,   -696,   -664,
	  -632,   -600,   -568,   -536,   -504,   -472,   -440,   -408,
	  -376,   -360,   -344,   -328,   -312,   -296,   -280,   -264,
	  -248,   -232,   -216,   -200,   -184,   -168,   -152,   -136,
	  -120,   -112,   -104,    -96,    -88,    -80,    -72,    -64,
	   -56,    -48,    -40,    -32,    -24,    -16,    -8,       0
    };

    /**
     * Uninstantiable class.
     */
    private AudioConversion() {}

    /**
     * Convert a ulaw byte array to a int array of linearData.
     *
     * @param ulawData   byte array with ulaw data
     * @param ulawOffset offset from the start of ulawData
     * @param length	 length of ulawData
     * @param linearData int array in which linear data will be placed
     * @param linearOffset offset from the start of linearData
     */
    public static void ulawToLinear(byte[] ulawData, int ulawOffset, int length,
            int[] linearData) {

	int inIx = ulawOffset;

        for (int i = 0; i < length; i++) {
	    linearData[i] = ulawToLinearTable[ulawData[inIx] + 128];
	    inIx++;
	}
    }

    public static void ulawToLinear(byte[] ulawData, int ulawOffset, int length,
            byte[] linearData) {

	ulawToLinear(ulawData, ulawOffset, length, linearData, 0);
    }

    public static void ulawToLinear(byte[] ulawData, int ulawOffset, int length,
            byte[] linearData, int linearOffset) {

	int inIx = ulawOffset;
 	int outIx = linearOffset;

        for (int i = 0; i < length; i++) {
            int s = ulawToLinearTable[ulawData[inIx] + 128];

            linearData[outIx] = (byte) ((s >> 8) & 0xff);
            outIx++;
            linearData[outIx] = (byte) (s & 0xff);
            outIx++;
            inIx++;
	}
    }

    /**
     * Convert a linear byte array to a ulaw byte array starting from offset
     * @param linearData byte array of linear data
     * @param offset	 integer offset from start of the arrays
     * @param ulawData   byte array in which ulaw data will be placed
     */
    public static void linearToUlaw(byte[] linearData,
                                    byte[] ulawData, int ulawOffset) {

	linearToUlaw(linearData, 0, ulawData, ulawOffset);
    }

    public static void linearToUlaw(byte[] linearData, int linearOffset,
                                    byte[] ulawData, int ulawOffset) {

        int outIx = ulawOffset;

        for (int inIx = linearOffset; inIx < linearData.length; inIx += 2)
        {
            //ulawData[outIx++] = linearToUlaw((linearData[inIx] << 8) | (linearData[inIx + 1] & 0xff)); // hack for jitsivideobridge
            ulawData[outIx++] = linearToUlaw((linearData[inIx + 1] << 8) | (linearData[inIx] & 0xff));
        }
    }

    /**
     * Convert a linear int array to a ulaw byte array starting from offset
     * @param linearData int array of linear data
     * @param offset	 integer offset from start of the arrays
     * @param ulawData   byte array in which ulaw data will be placed
     */
    public static void linearToUlaw(int[] linearData, byte[] ulawData,
	int ulawOffset) {

	int outIx = ulawOffset;

        for (int inIx = 0; inIx < linearData.length; inIx++) {
            ulawData[outIx++] = linearToUlaw(linearData[inIx]);
        }
    }

    /**
     * Convert a linear int to a ulaw byte
     *
     * @param sample int with linear data
     * @return       byte of ulaw data
     */
    public static byte linearToUlaw(int sample) {
	return linearToUlawTable[sample & 0xffff];
    }

    public static void intsToBytes(int[] intData, byte[] byteData,
	    int outOffset) {

        int outIx = outOffset;

        for (int i = 0; i < intData.length; i++) {
            byteData[outIx] = (byte) ((intData[i] >> 8) & 0xff);
            byteData[outIx + 1] = (byte) (intData[i] & 0xff);

            outIx += 2;
        }
    }

    public static byte[] littleEndianIntsToBytes(int[] intData) {
		byte[] byteData = new byte[intData.length * 2];

		int outIx = 0;

        for (int i = 0; i < intData.length; i++) {
            byteData[outIx + 1] = (byte) ((intData[i] >> 8) & 0xff);
            byteData[outIx] = (byte) (intData[i] & 0xff);

            outIx += 2;
        }

		return byteData;
    }

    public static byte[] intsToBytes(int[] intData) {
		byte[] byteData = new byte[intData.length * 2];

		int outIx = 0;

        for (int i = 0; i < intData.length; i++) {
            byteData[outIx] = (byte) ((intData[i] >> 8) & 0xff);
            byteData[outIx + 1] = (byte) (intData[i] & 0xff);

            outIx += 2;
        }

		return byteData;
    }

    public static int[] bytesToLittleEndianInts(byte[] byteData) {
		int[] intData = new int[byteData.length / 2];

        int inIx = 0;

        for (int i = 0; i < intData.length; i++) {
            short s = (short) (((byteData[inIx + 1] << 8) & 0xff00) | (byteData[inIx] & 0xff));
	    	intData[i] = (int) s;
            inIx += 2;
        }

		return intData;
    }

    public static int[] bytesToInts(byte[] byteData) {
	int[] intData = new int[byteData.length / 2];

        int inIx = 0;

        for (int i = 0; i < intData.length; i++) {
            short s = (short) (((byteData[inIx] << 8) & 0xff00) |
                (byteData[inIx + 1] & 0xff));

	    intData[i] = (int) s;

            inIx += 2;
        }

	return intData;
    }

    public static void bytesToInts(byte[] byteData, int byteDataOffset,
            int byteDataLength, int[] intData) {

        int inIx = byteDataOffset;

        for (int i = 0; i < intData.length; i++) {
            short s = (short) (((byteData[inIx] << 8) & 0xff00) |
                (byteData[inIx + 1] & 0xff));

	    intData[i] = (int) s;

            inIx += 2;
        }
    }


    public static short[] intsToShorts(int[] intData) {
	short[] shortData = new short[intData.length];

	for (int i = 0; i < intData.length; i++) {
	    shortData[i] = (short) intData[i];
	}

	return shortData;
    }

    public static int clip(int sample) {
        if (sample > 32767) {
	        Log.debug("Clip " + sample + " to 0x7fff (32767)");

            return 0x7fff;
        }

        if (sample < -32768) {

	    	Log.debug("Clip " + sample + " to 0xffff8000 (-32768)");

            return 0xffff8000;
        }

        return sample;
    }

    public static void clip(int[] data) {

	for (int i = 0; i < data.length; i++) {
	    data[i] = clip(data[i]);
	}
    }

}
