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

package org.xbill.DNS;

import java.io.*;
import java.util.*;

/**
 * A DNS message header
 *
 * @author Brian Wellington
 * @see Message
 */

public class Header implements Cloneable {

    private int id;
    private int flags;
    private int[] counts;

    private static Random random = new Random();

    /**
     * The length of a DNS Header in wire format.
     */
    public static final int LENGTH = 12;

    private void
    init() {
        counts = new int[4];
        flags = 0;
        id = -1;
    }

    /**
     * Create a new empty header.
     *
     * @param id The message id
     */
    public Header(int id) {
        init();
        setID(id);
    }

    /**
     * Create a new empty header with a random message id
     */
    public Header() {
        init();
    }

    /**
     * Parses a Header from a stream containing DNS wire format.
     */
    Header(DNSInput in) throws IOException {
        this(in.readU16());
        flags = in.readU16();
        for (int i = 0; i < counts.length; i++)
            counts[i] = in.readU16();
    }

    /**
     * Creates a new Header from its DNS wire format representation
     *
     * @param b A byte array containing the DNS Header.
     */
    public Header(byte[] b) throws IOException {
        this(new DNSInput(b));
    }

    void
    toWire(DNSOutput out) {
        out.writeU16(getID());
        out.writeU16(flags);
        for (int i = 0; i < counts.length; i++)
            out.writeU16(counts[i]);
    }

    public byte[]
    toWire() {
        DNSOutput out = new DNSOutput();
        toWire(out);
        return out.toByteArray();
    }

    static private boolean
    validFlag(int bit) {
        return (bit >= 0 && bit <= 0xF && Flags.isFlag(bit));
    }

    static private void
    checkFlag(int bit) {
        if (!validFlag(bit))
            throw new IllegalArgumentException("invalid flag bit " + bit);
    }

    /**
     * Sets a flag to the supplied value
     *
     * @see Flags
     */
    public void
    setFlag(int bit) {
        checkFlag(bit);
        // bits are indexed from left to right
        flags |= (1 << (15 - bit));
    }

    /**
     * Sets a flag to the supplied value
     *
     * @see Flags
     */
    public void
    unsetFlag(int bit) {
        checkFlag(bit);
        // bits are indexed from left to right
        flags &= ~(1 << (15 - bit));
    }

    /**
     * Retrieves a flag
     *
     * @see Flags
     */
    public boolean
    getFlag(int bit) {
        checkFlag(bit);
        // bits are indexed from left to right
        return (flags & (1 << (15 - bit))) != 0;
    }

    boolean[]
    getFlags() {
        boolean[] array = new boolean[16];
        for (int i = 0; i < array.length; i++)
            if (validFlag(i))
                array[i] = getFlag(i);
        return array;
    }

    /**
     * Retrieves the message ID
     */
    public int
    getID() {
        if (id >= 0)
            return id;
        synchronized (this) {
            if (id < 0)
                id = random.nextInt(0xffff);
            return id;
        }
    }

    /**
     * Sets the message ID
     */
    public void
    setID(int id) {
        if (id < 0 || id > 0xffff)
            throw new IllegalArgumentException("DNS message ID " + id +
                    " is out of range");
        this.id = id;
    }

    /**
     * Sets the message's rcode
     *
     * @see Rcode
     */
    public void
    setRcode(int value) {
        if (value < 0 || value > 0xF)
            throw new IllegalArgumentException("DNS Rcode " + value +
                    " is out of range");
        flags &= ~0xF;
        flags |= value;
    }

    /**
     * Retrieves the mesasge's rcode
     *
     * @see Rcode
     */
    public int
    getRcode() {
        return flags & 0xF;
    }

    /**
     * Sets the message's opcode
     *
     * @see Opcode
     */
    public void
    setOpcode(int value) {
        if (value < 0 || value > 0xF)
            throw new IllegalArgumentException("DNS Opcode " + value +
                    "is out of range");
        flags &= 0x87FF;
        flags |= (value << 11);
    }

    /**
     * Retrieves the mesasge's opcode
     *
     * @see Opcode
     */
    public int
    getOpcode() {
        return (flags >> 11) & 0xF;
    }

    void
    setCount(int field, int value) {
        if (value < 0 || value > 0xFFFF)
            throw new IllegalArgumentException("DNS section count " +
                    value + " is out of range");
        counts[field] = value;
    }

    void
    incCount(int field) {
        if (counts[field] == 0xFFFF)
            throw new IllegalStateException("DNS section count cannot " +
                    "be incremented");
        counts[field]++;
    }

    void
    decCount(int field) {
        if (counts[field] == 0)
            throw new IllegalStateException("DNS section count cannot " +
                    "be decremented");
        counts[field]--;
    }

    /**
     * Retrieves the record count for the given section
     *
     * @see Section
     */
    public int
    getCount(int field) {
        return counts[field];
    }

    /**
     * Converts the header's flags into a String
     */
    public String
    printFlags() {
        StringBuffer sb = new StringBuffer();

        for (int i = 0; i < 16; i++)
            if (validFlag(i) && getFlag(i)) {
                sb.append(Flags.string(i));
                sb.append(" ");
            }
        return sb.toString();
    }

    String
    toStringWithRcode(int newrcode) {
        StringBuffer sb = new StringBuffer();

        sb.append(";; ->>HEADER<<- ");
        sb.append("opcode: " + Opcode.string(getOpcode()));
        sb.append(", status: " + Rcode.string(newrcode));
        sb.append(", id: " + getID());
        sb.append("\n");

        sb.append(";; flags: " + printFlags());
        sb.append("; ");
        for (int i = 0; i < 4; i++)
            sb.append(Section.string(i) + ": " + getCount(i) + " ");
        return sb.toString();
    }

    /**
     * Converts the header into a String
     */
    public String
    toString() {
        return toStringWithRcode(getRcode());
    }

    /* Creates a new Header identical to the current one */
    public Object
    clone() {
        Header h = new Header();
        h.id = id;
        h.flags = flags;
        System.arraycopy(counts, 0, h.counts, 0, counts.length);
        return h;
    }

}
