/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.tck.wma.sms;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;

import java.io.IOException;

/**
 * A generic message packet that treats the packet as an input or output stream
 * of data.
 */
public class MessagePacket {

    /** The maximum payload size for a datagram. */
    private final int DATAGRAM_PACKET_LENGTH = 1500; 

    /** The current read-write index into the data buffer (stream). */
    private int index = 0;

    /** The data buffer (stream). */
    private byte[] data;

    private boolean firstWrite = true;

    /** The byte output stream, used when creating a new packet. */
    private ByteArrayOutputStream bos = null;

    /** The data output stream, used when creating a new packet. */
    private DataOutputStream dos = null;


    /**
     * Construct a new message packet that can be populated with some data.
     */
    public MessagePacket() {
        firstWrite = true;
    }

    /**
     * Construct a new message packet that contains the given payload.
     *
     * @param payload    The bytes to be read.
     */
    public MessagePacket(byte[] payload) {

        /* Make a clone of the payload (message) bytes. */
        int length = payload.length;
        data = new byte[length];
        for (int i = 0; i < length; i++) {
            data[i] = payload[i];
        }
    }

    /**
     * Read an integer (32-bit) value from the data stream, starting at the
     * current index.
     *
     * @return  The integer that was read from the data stream.
     *
     * @exception ArrayIndexOutOfBoundsException  if reading goes beyond the
     *     end of the data stream.
     */
    public int getInt() {
        int x1 = ((int)(data[index++] & 0xff));
        int x2 = ((int)(data[index++] & 0xff) << 8);
        int x3 = ((int)(data[index++] & 0xff) << 16);
        int x4 = ((int)(data[index++] & 0xff) << 24);
        return (x1 | x2 | x3 | x4);
    }

    /**
     * Write an integer (32-bit) value into the data stream and advance the
     * index.
     *
     * @param x    The integer to be written.
     *
     * @exception ArrayIndexOutOfBoundsException  if reading goes beyond the
     *     end of the data stream.
     */
    public void putInt(int x) throws IOException {
        checkFirstWrite();
        dos.writeByte((byte) (x & 0xff));
        dos.writeByte((byte)((x >> 8) & 0xff));
        dos.writeByte((byte)((x >> 16) & 0xff));
        dos.writeByte((byte)((x >> 24) & 0xff));
    }

    /**
     * Read a short (16-bit) value from the data stream, starting at the
     * current index.
     *
     * @return  The short that was read from the data stream.
     *
     * @exception ArrayIndexOutOfBoundsException  if reading goes beyond the
     *     end of the data stream.
     */
    public short getShort() {
        short x1 = ((short)(data[index++] & 0xff));
        short x2 = (short)((data[index++] & 0xff) << 8);
        return (short)(x1 | x2);
    }

    /**
     * Write a short (16-bit) value into the data stream and advance the
     * index.
     *
     * @param x    The short to be written.
     *
     * @exception ArrayIndexOutOfBoundsException  if reading goes beyond the
     *     end of the data stream.
     */
    public void putShort(short x) throws IOException {
        checkFirstWrite();
        dos.writeByte((byte) (x & 0xff));
        dos.writeByte((byte)((x >> 8) & 0xff));
    }

    /**
     * Read a long (64-bit) value from data stream, starting at the current
     * index.
     *
     * @return  The long value that was read from the data stream.
     *
     * @exception ArrayIndexOutOfBoundsException  if reading goes beyond the
     *     end of the data stream.
     */
    public long getLong() {
        long x1 = ((long)(data[index++] & 0xff));
        long x2 = ((long)(data[index++] & 0xff) << 8);
        long x3 = ((long)(data[index++] & 0xff) << 16);
        long x4 = ((long)(data[index++] & 0xff) << 24);
        long x5 = ((long)(data[index++] & 0xff) << 32);
        long x6 = ((long)(data[index++] & 0xff) << 40);
        long x7 = ((long)(data[index++] & 0xff) << 48);
        long x8 = ((long)(data[index++] & 0xff) << 56);
        return (x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8);
    }

    /**
     * Write a long (64-bit) value into the data stream and advance the index.
     *
     * @param x  The long to be written.
     *
     * @exception ArrayIndexOutOfBoundsException  if reading goes beyond the
     *     end of the data stream.
     */
    public void putLong(long x) throws IOException {
        checkFirstWrite();
        dos.writeByte((byte) (x & 0xff));
        dos.writeByte((byte)((x >> 8) & 0xff));
        dos.writeByte((byte)((x >> 16) & 0xff));
        dos.writeByte((byte)((x >> 24) & 0xff));
        dos.writeByte((byte)((x >> 32) & 0xff));
        dos.writeByte((byte)((x >> 40) & 0xff));
        dos.writeByte((byte)((x >> 48) & 0xff));
        dos.writeByte((byte)((x >> 56) & 0xff));
    }

    /**
     * Write a string to the data stream, starting at the current index. The
     * characters in the string are written, followed by a null-character
     * terminator.
     *
     * @param s  The string to be written.
     *
     * @exception ArrayIndexOutOfBoundsException  if reading goes beyond the
     *     end of the data stream.
     */
    public void putString(String s) throws IOException {
        checkFirstWrite();
        dos.writeBytes(s);
        dos.writeByte(0);
    }

    /**
     * Read a string from the data stream, starting at the current index. The
     * string is assumed to be terminated by a null character.
     *
     * @return  The string of characters, converted to a
     *          <code>java.lang.String</code> object.
     *
     * @exception ArrayIndexOutOfBoundsException  if reading goes beyond the
     *     end of the data stream.
     */
    public String getString() {
        int offset = index;
        int len = 0;

        /* Determine length of string */
        for (int i = index; i < data.length; i++) {
            if (data[i] == '\0') {
                break;
            }
            len++;
        }
             
        /* update index */
        index += len + 1;

        return new String(data, offset, len);
    }

    /**
     * Write an array of bytes into the data stream, starting at the current
     * index.
     *
     * @param buf  The buffer of bytes to be written into the data stream.
     *
     * @exception ArrayIndexOutOfBoundsException  if reading goes beyond the
     *     end of the data stream.
     */
    public void putBytes(byte[] buf) throws IOException {
        checkFirstWrite();
        dos.write(buf, 0, buf.length);
    }

    /**
     * Read an array of bytes from the data stream, starting at the current
     * index. The number of bytes to be read is specified by
     * <code>length</code>. The index is advanced by <code>length</code>.
     *
     * @param length  The number of bytes to be read.
     *
     * @return  The buffer of bytes that were read.
     *
     * @exception ArrayIndexOutOfBoundsException  if reading goes beyond the
     *     end of the data stream.
     */
    public byte[] getBytes(int length) {

        byte[] buffer = new byte[length];

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

        return buffer;
    }

    /**
     * Returns the contents of the packet as a byte array.
     *
     * @return The array of packet bytes.
     *
     * @exception IOException if there was a problem while closing the streams.
     */
    public byte[] getData() throws IOException {

        byte[] buffer = new byte[0];

        if (firstWrite == false) {
            dos.close();
            buffer = bos.toByteArray();
            bos.close();
        }

        return buffer;
    }

    /**
     * Checks to see if the data output streams need to be opened.
     */
    private void checkFirstWrite() {
        if (firstWrite == true) {
            firstWrite = false;
            bos = new ByteArrayOutputStream();
            dos = new DataOutputStream(bos);
        }
    }

}
