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

package org.xbill.DNS;

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

/**
 * A set of Records with the same name, type, and class.  Also included
 * are all RRSIG records signing the data records.
 *
 * @author Brian Wellington
 * @see Record
 * @see RRSIGRecord
 */

public class RRset implements Serializable {

    private static final long serialVersionUID = -3270249290171239695L;

    /*
     * rrs contains both normal and RRSIG records, with the RRSIG records
     * at the end.
     */
    private List rrs;
    private short nsigs;
    private short position;

    /**
     * Creates an empty RRset
     */
    public RRset() {
        rrs = new ArrayList(1);
        nsigs = 0;
        position = 0;
    }

    /**
     * Creates an RRset and sets its contents to the specified record
     */
    public RRset(Record record) {
        this();
        safeAddRR(record);
    }

    /**
     * Creates an RRset with the contents of an existing RRset
     */
    public RRset(RRset rrset) {
        synchronized (rrset) {
            rrs = (List) ((ArrayList) rrset.rrs).clone();
            nsigs = rrset.nsigs;
            position = rrset.position;
        }
    }

    private void
    safeAddRR(Record r) {
        if (!(r instanceof RRSIGRecord)) {
            if (nsigs == 0)
                rrs.add(r);
            else
                rrs.add(rrs.size() - nsigs, r);
        } else {
            rrs.add(r);
            nsigs++;
        }
    }

    /**
     * Adds a Record to an RRset
     */
    public synchronized void
    addRR(Record r) {
        if (rrs.size() == 0) {
            safeAddRR(r);
            return;
        }
        Record first = first();
        if (!r.sameRRset(first))
            throw new IllegalArgumentException("record does not match " +
                    "rrset");

        if (r.getTTL() != first.getTTL()) {
            if (r.getTTL() > first.getTTL()) {
                r = r.cloneRecord();
                r.setTTL(first.getTTL());
            } else {
                for (int i = 0; i < rrs.size(); i++) {
                    Record tmp = (Record) rrs.get(i);
                    tmp = tmp.cloneRecord();
                    tmp.setTTL(r.getTTL());
                    rrs.set(i, tmp);
                }
            }
        }

        if (!rrs.contains(r))
            safeAddRR(r);
    }

    /**
     * Deletes a Record from an RRset
     */
    public synchronized void
    deleteRR(Record r) {
        if (rrs.remove(r) && (r instanceof RRSIGRecord))
            nsigs--;
    }

    /**
     * Deletes all Records from an RRset
     */
    public synchronized void
    clear() {
        rrs.clear();
        position = 0;
        nsigs = 0;
    }

    private synchronized Iterator
    iterator(boolean data, boolean cycle) {
        int size, start, total;

        total = rrs.size();

        if (data)
            size = total - nsigs;
        else
            size = nsigs;
        if (size == 0)
            return Collections.EMPTY_LIST.iterator();

        if (data) {
            if (!cycle)
                start = 0;
            else {
                if (position >= size)
                    position = 0;
                start = position++;
            }
        } else {
            start = total - nsigs;
        }

        List list = new ArrayList(size);
        if (data) {
            list.addAll(rrs.subList(start, size));
            if (start != 0)
                list.addAll(rrs.subList(0, start));
        } else {
            list.addAll(rrs.subList(start, total));
        }

        return list.iterator();
    }

    /**
     * Returns an Iterator listing all (data) records.
     *
     * @param cycle If true, cycle through the records so that each Iterator will
     *              start with a different record.
     */
    public synchronized Iterator
    rrs(boolean cycle) {
        return iterator(true, cycle);
    }

    /**
     * Returns an Iterator listing all (data) records.  This cycles through
     * the records, so each Iterator will start with a different record.
     */
    public synchronized Iterator
    rrs() {
        return iterator(true, true);
    }

    /**
     * Returns an Iterator listing all signature records
     */
    public synchronized Iterator
    sigs() {
        return iterator(false, false);
    }

    /**
     * Returns the number of (data) records
     */
    public synchronized int
    size() {
        return rrs.size() - nsigs;
    }

    /**
     * Returns the name of the records
     *
     * @see Name
     */
    public Name
    getName() {
        return first().getName();
    }

    /**
     * Returns the type of the records
     *
     * @see Type
     */
    public int
    getType() {
        return first().getRRsetType();
    }

    /**
     * Returns the class of the records
     *
     * @see DClass
     */
    public int
    getDClass() {
        return first().getDClass();
    }

    /**
     * Returns the ttl of the records
     */
    public synchronized long
    getTTL() {
        return first().getTTL();
    }

    /**
     * Returns the first record
     *
     * @throws IllegalStateException if the rrset is empty
     */
    public synchronized Record
    first() {
        if (rrs.size() == 0)
            throw new IllegalStateException("rrset is empty");
        return (Record) rrs.get(0);
    }

    private String
    iteratorToString(Iterator it) {
        StringBuffer sb = new StringBuffer();
        while (it.hasNext()) {
            Record rr = (Record) it.next();
            sb.append("[");
            sb.append(rr.rdataToString());
            sb.append("]");
            if (it.hasNext())
                sb.append(" ");
        }
        return sb.toString();
    }

    /**
     * Converts the RRset to a String
     */
    public String
    toString() {
        if (rrs == null)
            return ("{empty}");
        StringBuffer sb = new StringBuffer();
        sb.append("{ ");
        sb.append(getName() + " ");
        sb.append(getTTL() + " ");
        sb.append(DClass.string(getDClass()) + " ");
        sb.append(Type.string(getType()) + " ");
        sb.append(iteratorToString(iterator(true, false)));
        if (nsigs > 0) {
            sb.append(" sigs: ");
            sb.append(iteratorToString(iterator(false, false)));
        }
        sb.append(" }");
        return sb.toString();
    }

}
