/*
 * Copyright (C) 2005 Alfresco, Inc.
 *
 * Licensed under the Mozilla Public License version 1.1 
 * with a permitted attribution clause. You may obtain a
 * copy of the License at
 *
 *   http://www.alfresco.org/legal/license.txt
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the
 * License.
 */
package org.alfresco.filesys.util;

/**
 * The data packing class is a static class that is used to pack and unpack basic data types to/from
 * network byte order and Intel byte order.
 */
public final class DataPacker
{

    // Flag to indicate the byte order of the platform that we are currently
    // running on.

    private static boolean bigEndian = false;

    /**
     * Return the current endian setting.
     * 
     * @return true if the system is big endian, else false.
     */
    public final static boolean isBigEndian()
    {
        return bigEndian;
    }

    /**
     * Unpack a null terminated data string from the data buffer.
     * 
     * @param typ Data type, as specified by SMBDataType.
     * @param bytarray Byte array to unpack the string value from.
     * @param pos Offset to start unpacking the string value.
     * @param maxlen Maximum length of data to be searched for a null character.
     * @param uni String is Unicode if true, else ASCII
     * @return String, else null if the terminating null character was not found.
     */
    public final static String getDataString(char typ, byte[] bytarray, int pos, int maxlen, boolean uni)
    {

        // Check if the data string has the required data type

        if (bytarray[pos++] == (byte) typ)
        {

            // Extract the null terminated string

            if (uni == true)
                return getUnicodeString(bytarray, wordAlign(pos), maxlen / 2);
            else
                return getString(bytarray, pos, maxlen - 1);
        }

        // Invalid data type

        return null;
    }

    /**
     * Unpack a 32-bit integer.
     * 
     * @param buf Byte buffer containing the integer to be unpacked.
     * @param pos Position within the buffer that the integer is stored.
     * @return The unpacked 32-bit integer value.
     * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
     */
    public final static int getInt(byte[] buf, int pos) throws java.lang.IndexOutOfBoundsException
    {

        // Check if the byte array is long enough

        if (buf.length < pos + 3)
            throw new java.lang.IndexOutOfBoundsException();

        // Unpack the 32-bit value

        int i1 = (int) buf[pos] & 0xFF;
        int i2 = (int) buf[pos + 1] & 0xFF;
        int i3 = (int) buf[pos + 2] & 0xFF;
        int i4 = (int) buf[pos + 3] & 0xFF;

        int iVal = (i1 << 24) + (i2 << 16) + (i3 << 8) + i4;

        // Return the unpacked value

        return iVal;
    }

    /**
     * Unpack a 32-bit integer that is stored in Intel format.
     * 
     * @param bytarray Byte array containing the Intel integer to be unpacked.
     * @param pos Offset that the Intel integer is stored within the byte array.
     * @return Unpacked integer value.
     * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
     */
    public final static int getIntelInt(byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException
    {

        // Check if the byte array is long enough to restore the int

        if (bytarray.length < pos + 3)
            throw new java.lang.IndexOutOfBoundsException();

        // Determine the byte ordering for this platform, and restore the int

        int iVal = 0;

        // Restore the int value from the byte array

        int i1 = (int) bytarray[pos + 3] & 0xFF;
        int i2 = (int) bytarray[pos + 2] & 0xFF;
        int i3 = (int) bytarray[pos + 1] & 0xFF;
        int i4 = (int) bytarray[pos] & 0xFF;

        iVal = (i1 << 24) + (i2 << 16) + (i3 << 8) + i4;

        // Return the int value

        return iVal;
    }

    /**
     * Unpack a 64-bit long.
     * 
     * @param buf Byte buffer containing the integer to be unpacked.
     * @param pos Position within the buffer that the integer is stored.
     * @return The unpacked 64-bit long value.
     * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
     */
    public final static long getLong(byte[] buf, int pos) throws java.lang.IndexOutOfBoundsException
    {

        // Check if the byte array is long enough to restore the long

        if (buf.length < pos + 7)
            throw new java.lang.IndexOutOfBoundsException();

        // Restore the long value from the byte array

        long lVal = 0L;

        for (int i = 0; i < 8; i++)
        {

            // Get the current byte, shift the value and add to the return value

            long curVal = (long) buf[pos + i] & 0xFF;
            curVal = curVal << ((7 - i) * 8);
            lVal += curVal;
        }

        // Return the long value

        return lVal;
    }

    /**
     * Unpack a 64-bit integer that is stored in Intel format.
     * 
     * @param bytarray Byte array containing the Intel long to be unpacked.
     * @param pos Offset that the Intel integer is stored within the byte array.
     * @return Unpacked long value.
     * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
     */
    public final static long getIntelLong(byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException
    {

        // Check if the byte array is long enough to restore the long

        if (bytarray.length < pos + 7)
            throw new java.lang.IndexOutOfBoundsException();

        // Restore the long value from the byte array

        long lVal = 0L;

        for (int i = 0; i < 8; i++)
        {

            // Get the current byte, shift the value and add to the return value

            long curVal = (long) bytarray[pos + i] & 0xFF;
            curVal = curVal << (i * 8);
            lVal += curVal;
        }

        // Return the long value

        return lVal;
    }

    /**
     * Unpack a 16-bit value that is stored in Intel format.
     * 
     * @param bytarray Byte array containing the short value to be unpacked.
     * @param pos Offset to start unpacking the short value.
     * @return Unpacked short value.
     * @exception java.lang.IndexOutOfBoiundsException If there is not enough data in the buffer.
     */
    public final static int getIntelShort(byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException
    {

        // Check if the byte array is long enough to restore the int

        if (bytarray.length < pos)
            throw new java.lang.IndexOutOfBoundsException();

        // Restore the short value from the byte array

        int sVal = (((int) bytarray[pos + 1] << 8) + ((int) bytarray[pos] & 0xFF));

        // Return the short value

        return sVal & 0xFFFF;
    }

    /**
     * Unpack a 16-bit value.
     * 
     * @param bytarray Byte array containing the short to be unpacked.
     * @param pos Offset within the byte array that the short is stored.
     * @return Unpacked short value.
     * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
     */
    public final static int getShort(byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException
    {

        // Check if the byte array is long enough to restore the int

        if (bytarray.length < pos)
            throw new java.lang.IndexOutOfBoundsException();

        // Determine the byte ordering for this platform, and restore the short

        int sVal = 0;

        if (bigEndian == true)
        {

            // Big endian

            sVal = ((((int) bytarray[pos + 1]) << 8) + ((int) bytarray[pos] & 0xFF));
        }
        else
        {

            // Little endian

            sVal = ((((int) bytarray[pos]) << 8) + ((int) bytarray[pos + 1] & 0xFF));
        }

        // Return the short value

        return sVal & 0xFFFF;
    }

    /**
     * Unpack a null terminated string from the data buffer.
     * 
     * @param bytarray Byte array to unpack the string value from.
     * @param pos Offset to start unpacking the string value.
     * @param maxlen Maximum length of data to be searched for a null character.
     * @return String, else null if the terminating null character was not found.
     */
    public final static String getString(byte[] bytarray, int pos, int maxlen)
    {

        // Search for the trailing null

        int maxpos = pos + maxlen;
        int endpos = pos;

        while (bytarray[endpos] != 0x00 && endpos < maxpos)
            endpos++;

        // Check if we reached the end of the buffer

        if (endpos <= maxpos)
            return new String(bytarray, pos, endpos - pos);
        return null;
    }

    /**
     * Unpack a null terminated string from the data buffer. The string may be ASCII or Unicode.
     * 
     * @param bytarray Byte array to unpack the string value from.
     * @param pos Offset to start unpacking the string value.
     * @param maxlen Maximum length of data to be searched for a null character.
     * @param isUni Unicode string if true, else ASCII string
     * @return String, else null if the terminating null character was not found.
     */
    public final static String getString(byte[] bytarray, int pos, int maxlen, boolean isUni)
    {

        // Get a string from the buffer

        String str = null;

        if (isUni)
            str = getUnicodeString(bytarray, pos, maxlen);
        else
            str = getString(bytarray, pos, maxlen);

        // return the string

        return str;
    }

    /**
     * Unpack a null terminated Unicode string from the data buffer.
     * 
     * @param byt Byte array to unpack the string value from.
     * @param pos Offset to start unpacking the string value.
     * @param maxlen Maximum length of data to be searched for a null character.
     * @return String, else null if the terminating null character was not found.
     */
    public final static String getUnicodeString(byte[] byt, int pos, int maxlen)
    {

        // Check for an empty string

        if (maxlen == 0)
            return "";

        // Search for the trailing null

        int maxpos = pos + (maxlen * 2);
        int endpos = pos;
        char[] chars = new char[maxlen];
        int cpos = 0;
        char curChar;

        do
        {

            // Get a Unicode character from the buffer

            curChar = (char) (((byt[endpos + 1] & 0xFF) << 8) + (byt[endpos] & 0xFF));

            // Add the character to the array

            chars[cpos++] = curChar;

            // Update the buffer pointer

            endpos += 2;

        } while (curChar != 0 && endpos < maxpos);

        // Check if we reached the end of the buffer

        if (endpos <= maxpos)
        {
            if (curChar == 0)
                cpos--;
            return new String(chars, 0, cpos);
        }
        return null;
    }

    /**
     * Pack a 32-bit integer into the supplied byte buffer.
     * 
     * @param val Integer value to be packed.
     * @param bytarray Byte buffer to pack the integer value into.
     * @param pos Offset to start packing the integer value.
     * @exception java.lang.IndexOutOfBoundsException If the buffer does not have enough space.
     */
    public final static void putInt(int val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException
    {

        // Check if the byte array is long enough to store the int

        if (bytarray.length < pos + 3)
            throw new java.lang.IndexOutOfBoundsException();

        // Pack the integer value

        bytarray[pos] = (byte) ((val >> 24) & 0xFF);
        bytarray[pos + 1] = (byte) ((val >> 16) & 0xFF);
        bytarray[pos + 2] = (byte) ((val >> 8) & 0xFF);
        bytarray[pos + 3] = (byte) (val & 0xFF);
    }

    /**
     * Pack an 32-bit integer value in Intel format.
     * 
     * @param val Integer value to be packed.
     * @param bytarray Byte array to pack the value into.
     * @param pos Offset to start packing the integer value.
     * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
     */
    public final static void putIntelInt(int val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException
    {

        // Check if the byte array is long enough to store the int

        if (bytarray.length < pos + 3)
            throw new java.lang.IndexOutOfBoundsException();

        // Store the int value in the byte array

        bytarray[pos + 3] = (byte) ((val >> 24) & 0xFF);
        bytarray[pos + 2] = (byte) ((val >> 16) & 0xFF);
        bytarray[pos + 1] = (byte) ((val >> 8) & 0xFF);
        bytarray[pos] = (byte) (val & 0xFF);
    }

    /**
     * Pack a 64-bit integer value into the buffer
     * 
     * @param val Integer value to be packed.
     * @param bytarray Byte array to pack the value into.
     * @param pos Offset to start packing the integer value.
     * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
     */
    public final static void putLong(long val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException
    {

        // Check if the byte array is long enough to store the int

        if (bytarray.length < pos + 7)
            throw new java.lang.IndexOutOfBoundsException();

        // Store the long value in the byte array

        bytarray[pos] = (byte) ((val >> 56) & 0xFF);
        bytarray[pos + 1] = (byte) ((val >> 48) & 0xFF);
        bytarray[pos + 2] = (byte) ((val >> 40) & 0xFF);
        bytarray[pos + 3] = (byte) ((val >> 32) & 0xFF);
        bytarray[pos + 4] = (byte) ((val >> 24) & 0xFF);
        bytarray[pos + 5] = (byte) ((val >> 16) & 0xFF);
        bytarray[pos + 6] = (byte) ((val >> 8) & 0xFF);
        bytarray[pos + 7] = (byte) (val & 0xFF);
    }

    /**
     * Pack a 64-bit integer value in Intel format.
     * 
     * @param val Integer value to be packed.
     * @param bytarray Byte array to pack the value into.
     * @param pos Offset to start packing the integer value.
     * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
     */
    public final static void putIntelLong(long val, byte[] bytarray, int pos)
            throws java.lang.IndexOutOfBoundsException
    {

        // Check if the byte array is long enough to store the int

        if (bytarray.length < pos + 7)
            throw new java.lang.IndexOutOfBoundsException();

        // Store the long value in the byte array

        bytarray[pos + 7] = (byte) ((val >> 56) & 0xFF);
        bytarray[pos + 6] = (byte) ((val >> 48) & 0xFF);
        bytarray[pos + 5] = (byte) ((val >> 40) & 0xFF);
        bytarray[pos + 4] = (byte) ((val >> 32) & 0xFF);
        bytarray[pos + 3] = (byte) ((val >> 24) & 0xFF);
        bytarray[pos + 2] = (byte) ((val >> 16) & 0xFF);
        bytarray[pos + 1] = (byte) ((val >> 8) & 0xFF);
        bytarray[pos] = (byte) (val & 0xFF);
    }

    /**
     * Pack a 64-bit integer value in Intel format.
     * 
     * @param val Integer value to be packed.
     * @param bytarray Byte array to pack the value into.
     * @param pos Offset to start packing the integer value.
     * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
     */
    public final static void putIntelLong(int val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException
    {

        // Check if the byte array is long enough to store the int

        if (bytarray.length < pos + 7)
            throw new java.lang.IndexOutOfBoundsException();

        // Store the int value in the byte array

        bytarray[pos + 7] = (byte) 0;
        bytarray[pos + 6] = (byte) 0;
        bytarray[pos + 5] = (byte) 0;
        bytarray[pos + 4] = (byte) 0;
        bytarray[pos + 3] = (byte) ((val >> 24) & 0xFF);
        bytarray[pos + 2] = (byte) ((val >> 16) & 0xFF);
        bytarray[pos + 1] = (byte) ((val >> 8) & 0xFF);
        bytarray[pos] = (byte) (val & 0xFF);
    }

    /**
     * Pack a 16 bit value in Intel byte order.
     * 
     * @param val Short value to be packed.
     * @param bytarray Byte array to pack the short value into.
     * @param pos Offset to start packing the short value.
     * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
     */
    public final static void putIntelShort(int val, byte[] bytarray, int pos)
            throws java.lang.IndexOutOfBoundsException
    {

        // Check if the byte array is long enough to store the short

        if (bytarray.length < pos)
            throw new java.lang.IndexOutOfBoundsException();

        // Pack the short value

        bytarray[pos + 1] = (byte) ((val >> 8) & 0xFF);
        bytarray[pos] = (byte) (val & 0xFF);
    }

    /**
     * Pack a 16-bit value into the supplied byte buffer.
     * 
     * @param val Short value to be packed.
     * @param bytarray Byte array to pack the short value into.
     * @param pos Offset to start packing the short value.
     * @exception java.lang.IndexOutOfBoundsException If there is not enough data in the buffer.
     */
    public final static void putShort(int val, byte[] bytarray, int pos) throws java.lang.IndexOutOfBoundsException
    {

        // Check if the byte array is long enough to store the short

        if (bytarray.length < pos)
            throw new java.lang.IndexOutOfBoundsException();

        // Pack the short value

        bytarray[pos] = (byte) ((val >> 8) & 0xFF);
        bytarray[pos + 1] = (byte) (val & 0xFF);
    }

    /**
     * Pack a string into a data buffer
     * 
     * @param str String to be packed into the buffer
     * @param bytarray Byte array to pack the string into
     * @param pos Position to start packing the string
     * @param nullterm true if the string should be null terminated, else false
     * @return The ending buffer position
     */
    public final static int putString(String str, byte[] bytarray, int pos, boolean nullterm)
    {

        // Get the string as a byte array

        byte[] byts = str.getBytes();

        // Pack the data bytes

        int bufpos = pos;

        for (int i = 0; i < byts.length; i++)
            bytarray[bufpos++] = byts[i];

        // Null terminate the string, if required

        if (nullterm == true)
            bytarray[bufpos++] = 0;

        // Return the next free buffer position

        return bufpos;
    }

    /**
     * Pack a string into a data buffer
     * 
     * @param str String to be packed into the buffer
     * @param fldLen Field length, will be space padded if short
     * @param bytarray Byte array to pack the string into
     * @param pos Position to start packing the string
     * @return The ending buffer position
     */
    public final static int putString(String str, int fldLen, byte[] bytarray, int pos)
    {

        // Get the string as a byte array

        byte[] byts = str.getBytes();

        // Pack the data bytes

        int bufpos = pos;
        int idx = 0;

        while (idx < fldLen)
        {
            if (idx < byts.length)
                bytarray[bufpos++] = byts[idx];
            else
                bytarray[bufpos++] = (byte) 0;
            idx++;
        }

        // Return the next free buffer position

        return bufpos;
    }

    /**
     * Pack a string into a data buffer. The string may be ASCII or Unicode.
     * 
     * @param str String to be packed into the buffer
     * @param bytarray Byte array to pack the string into
     * @param pos Position to start packing the string
     * @param nullterm true if the string should be null terminated, else false
     * @param isUni true if the string should be packed as Unicode, false to pack as ASCII
     * @return The ending buffer position
     */
    public final static int putString(String str, byte[] bytarray, int pos, boolean nullterm, boolean isUni)
    {

        // Pack the string

        int newpos = -1;

        if (isUni)
            newpos = putUnicodeString(str, bytarray, pos, nullterm);
        else
            newpos = putString(str, bytarray, pos, nullterm);

        // Return the end of string buffer position

        return newpos;
    }

    /**
     * Pack a Unicode string into a data buffer
     * 
     * @param str String to be packed into the buffer
     * @param bytarray Byte array to pack the string into
     * @param pos Position to start packing the string
     * @param nullterm true if the string should be null terminated, else false
     * @return The ending buffer position
     */
    public final static int putUnicodeString(String str, byte[] bytarray, int pos, boolean nullterm)
    {

        // Pack the data bytes

        int bufpos = pos;

        for (int i = 0; i < str.length(); i++)
        {

            // Get the current character from the string

            char ch = str.charAt(i);

            // Pack the unicode character

            bytarray[bufpos++] = (byte) (ch & 0xFF);
            bytarray[bufpos++] = (byte) ((ch & 0xFF00) >> 8);
        }

        // Null terminate the string, if required

        if (nullterm == true)
        {
            bytarray[bufpos++] = 0;
            bytarray[bufpos++] = 0;
        }

        // Return the next free buffer position

        return bufpos;
    }

    /**
     * Pack nulls into the buffer.
     * 
     * @param buf Buffer to pack data into.
     * @param pos Position to start packing.
     * @param cnt Number of nulls to pack.
     * @exception java.lang.ArrayIndexOutOfBoundsException If the buffer does not have enough space.
     */
    public final static void putZeros(byte[] buf, int pos, int cnt) throws java.lang.ArrayIndexOutOfBoundsException
    {

        // Check if the buffer is big enough

        if (buf.length < (pos + cnt))
            throw new java.lang.ArrayIndexOutOfBoundsException();

        // Pack the nulls

        for (int i = 0; i < cnt; i++)
            buf[pos + i] = 0;
    }

    /**
     * Align a buffer offset on a word boundary
     * 
     * @param pos int
     * @return int
     */
    public final static int wordAlign(int pos)
    {
        return (pos + 1) & 0xFFFFFFFE;
    }

    /**
     * Align a buffer offset on a longword boundary
     * 
     * @param pos int
     * @return int
     */
    public final static int longwordAlign(int pos)
    {
        return (pos + 3) & 0xFFFFFFFC;
    }

    /**
     * Calculate the string length in bytes
     * 
     * @param str String
     * @param uni boolean
     * @param nul boolean
     * @return int
     */
    public final static int getStringLength(String str, boolean uni, boolean nul)
    {

        // Calculate the string length in bytes

        int len = str.length();
        if (nul)
            len += 1;
        if (uni)
            len *= 2;

        return len;
    }

    /**
     * Calculate the new buffer position after the specified string and encoding (ASCII or Unicode)
     * 
     * @param pos int
     * @param str String
     * @param uni boolean
     * @param nul boolean
     * @return int
     */
    public final static int getBufferPosition(int pos, String str, boolean uni, boolean nul)
    {

        // Calculate the new buffer position

        int len = str.length();
        if (nul)
            len += 1;
        if (uni)
            len *= 2;

        return pos + len;
    }
}