// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)

package org.xbill.DNS;

/**
 * A class for rendering DNS messages.
 *
 * @author Brian Wellington
 */


public class DNSOutput {

    private byte[] array;
    private int pos;
    private int saved_pos;

    /**
     * Create a new DNSOutput with a specified size.
     *
     * @param size The initial size
     */
    public DNSOutput(int size) {
        array = new byte[size];
        pos = 0;
        saved_pos = -1;
    }

    /**
     * Create a new DNSOutput
     */
    public DNSOutput() {
        this(32);
    }

    /**
     * Returns the current position.
     */
    public int
    current() {
        return pos;
    }

    private void
    check(long val, int bits) {
        long max = 1;
        max <<= bits;
        if (val < 0 || val > max) {
            throw new IllegalArgumentException(val + " out of range for " +
                    bits + " bit value");
        }
    }

    private void
    need(int n) {
        if (array.length - pos >= n) {
            return;
        }
        int newsize = array.length * 2;
        if (newsize < pos + n) {
            newsize = pos + n;
        }
        byte[] newarray = new byte[newsize];
        System.arraycopy(array, 0, newarray, 0, pos);
        array = newarray;
    }

    /**
     * Resets the current position of the output stream to the specified index.
     *
     * @param index The new current position.
     * @throws IllegalArgumentException The index is not within the output.
     */
    public void
    jump(int index) {
        if (index > pos) {
            throw new IllegalArgumentException("cannot jump past " +
                    "end of data");
        }
        pos = index;
    }

    /**
     * Saves the current state of the output stream.
     *
     * @throws IllegalArgumentException The index is not within the output.
     */
    public void
    save() {
        saved_pos = pos;
    }

    /**
     * Restores the input stream to its state before the call to {@link #save}.
     */
    public void
    restore() {
        if (saved_pos < 0) {
            throw new IllegalStateException("no previous state");
        }
        pos = saved_pos;
        saved_pos = -1;
    }

    /**
     * Writes an unsigned 8 bit value to the stream.
     *
     * @param val The value to be written
     */
    public void
    writeU8(int val) {
        check(val, 8);
        need(1);
        array[pos++] = (byte) (val & 0xFF);
    }

    /**
     * Writes an unsigned 16 bit value to the stream.
     *
     * @param val The value to be written
     */
    public void
    writeU16(int val) {
        check(val, 16);
        need(2);
        array[pos++] = (byte) ((val >>> 8) & 0xFF);
        array[pos++] = (byte) (val & 0xFF);
    }

    /**
     * Writes an unsigned 16 bit value to the specified position in the stream.
     *
     * @param val   The value to be written
     * @param where The position to write the value.
     */
    public void
    writeU16At(int val, int where) {
        check(val, 16);
        if (where > pos - 2)
            throw new IllegalArgumentException("cannot write past " +
                    "end of data");
        array[where++] = (byte) ((val >>> 8) & 0xFF);
        array[where++] = (byte) (val & 0xFF);
    }

    /**
     * Writes an unsigned 32 bit value to the stream.
     *
     * @param val The value to be written
     */
    public void
    writeU32(long val) {
        check(val, 32);
        need(4);
        array[pos++] = (byte) ((val >>> 24) & 0xFF);
        array[pos++] = (byte) ((val >>> 16) & 0xFF);
        array[pos++] = (byte) ((val >>> 8) & 0xFF);
        array[pos++] = (byte) (val & 0xFF);
    }

    /**
     * Writes a byte array to the stream.
     *
     * @param b   The array to write.
     * @param off The offset of the array to start copying data from.
     * @param len The number of bytes to write.
     */
    public void
    writeByteArray(byte[] b, int off, int len) {
        need(len);
        System.arraycopy(b, off, array, pos, len);
        pos += len;
    }

    /**
     * Writes a byte array to the stream.
     *
     * @param b The array to write.
     */
    public void
    writeByteArray(byte[] b) {
        writeByteArray(b, 0, b.length);
    }

    /**
     * Writes a counted string from the stream.  A counted string is a one byte
     * value indicating string length, followed by bytes of data.
     *
     * @param s The string to write.
     */
    public void
    writeCountedString(byte[] s) {
        if (s.length > 0xFF) {
            throw new IllegalArgumentException("Invalid counted string");
        }
        need(1 + s.length);
        array[pos++] = (byte) (s.length & 0xFF);
        writeByteArray(s, 0, s.length);
    }

    /**
     * Returns a byte array containing the current contents of the stream.
     */
    public byte[]
    toByteArray() {
        byte[] out = new byte[pos];
        System.arraycopy(array, 0, out, 0, pos);
        return out;
    }

}
