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

package org.xbill.DNS;

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

/**
 * A helper class for constructing dynamic DNS (DDNS) update messages.
 *
 * @author Brian Wellington
 */

public class Update extends Message {

    private Name origin;
    private int dclass;

    /**
     * Creates an update message.
     *
     * @param zone   The name of the zone being updated.
     * @param dclass The class of the zone being updated.
     */
    public Update(Name zone, int dclass) {
        super();
        if (!zone.isAbsolute())
            throw new RelativeNameException(zone);
        DClass.check(dclass);
        getHeader().setOpcode(Opcode.UPDATE);
        Record soa = Record.newRecord(zone, Type.SOA, DClass.IN);
        addRecord(soa, Section.QUESTION);
        this.origin = zone;
        this.dclass = dclass;
    }

    /**
     * Creates an update message.  The class is assumed to be IN.
     *
     * @param zone The name of the zone being updated.
     */
    public Update(Name zone) {
        this(zone, DClass.IN);
    }

    private void
    newPrereq(Record rec) {
        addRecord(rec, Section.PREREQ);
    }

    private void
    newUpdate(Record rec) {
        addRecord(rec, Section.UPDATE);
    }

    /**
     * Inserts a prerequisite that the specified name exists; that is, there
     * exist records with the given name in the zone.
     */
    public void
    present(Name name) {
        newPrereq(Record.newRecord(name, Type.ANY, DClass.ANY, 0));
    }

    /**
     * Inserts a prerequisite that the specified rrset exists; that is, there
     * exist records with the given name and type in the zone.
     */
    public void
    present(Name name, int type) {
        newPrereq(Record.newRecord(name, type, DClass.ANY, 0));
    }

    /**
     * Parses a record from the string, and inserts a prerequisite that the
     * record exists.  Due to the way value-dependent prequisites work, the
     * condition that must be met is that the set of all records with the same
     * and type in the update message must be identical to the set of all records
     * with that name and type on the server.
     *
     * @throws IOException The record could not be parsed.
     */
    public void
    present(Name name, int type, String record) throws IOException {
        newPrereq(Record.fromString(name, type, dclass, 0, record, origin));
    }

    /**
     * Parses a record from the tokenizer, and inserts a prerequisite that the
     * record exists.  Due to the way value-dependent prequisites work, the
     * condition that must be met is that the set of all records with the same
     * and type in the update message must be identical to the set of all records
     * with that name and type on the server.
     *
     * @throws IOException The record could not be parsed.
     */
    public void
    present(Name name, int type, Tokenizer tokenizer) throws IOException {
        newPrereq(Record.fromString(name, type, dclass, 0, tokenizer, origin));
    }

    /**
     * Inserts a prerequisite that the specified record exists.  Due to the way
     * value-dependent prequisites work, the condition that must be met is that
     * the set of all records with the same and type in the update message must
     * be identical to the set of all records with that name and type on the server.
     */
    public void
    present(Record record) {
        newPrereq(record);
    }

    /**
     * Inserts a prerequisite that the specified name does not exist; that is,
     * there are no records with the given name in the zone.
     */
    public void
    absent(Name name) {
        newPrereq(Record.newRecord(name, Type.ANY, DClass.NONE, 0));
    }

    /**
     * Inserts a prerequisite that the specified rrset does not exist; that is,
     * there are no records with the given name and type in the zone.
     */
    public void
    absent(Name name, int type) {
        newPrereq(Record.newRecord(name, type, DClass.NONE, 0));
    }

    /**
     * Parses a record from the string, and indicates that the record
     * should be inserted into the zone.
     *
     * @throws IOException The record could not be parsed.
     */
    public void
    add(Name name, int type, long ttl, String record) throws IOException {
        newUpdate(Record.fromString(name, type, dclass, ttl, record, origin));
    }

    /**
     * Parses a record from the tokenizer, and indicates that the record
     * should be inserted into the zone.
     *
     * @throws IOException The record could not be parsed.
     */
    public void
    add(Name name, int type, long ttl, Tokenizer tokenizer) throws IOException {
        newUpdate(Record.fromString(name, type, dclass, ttl, tokenizer,
                origin));
    }

    /**
     * Indicates that the record should be inserted into the zone.
     */
    public void
    add(Record record) {
        newUpdate(record);
    }

    /**
     * Indicates that the records should be inserted into the zone.
     */
    public void
    add(Record[] records) {
        for (int i = 0; i < records.length; i++)
            add(records[i]);
    }

    /**
     * Indicates that all of the records in the rrset should be inserted into the
     * zone.
     */
    public void
    add(RRset rrset) {
        for (Iterator it = rrset.rrs(); it.hasNext(); )
            add((Record) it.next());
    }

    /**
     * Indicates that all records with the given name should be deleted from
     * the zone.
     */
    public void
    delete(Name name) {
        newUpdate(Record.newRecord(name, Type.ANY, DClass.ANY, 0));
    }

    /**
     * Indicates that all records with the given name and type should be deleted
     * from the zone.
     */
    public void
    delete(Name name, int type) {
        newUpdate(Record.newRecord(name, type, DClass.ANY, 0));
    }

    /**
     * Parses a record from the string, and indicates that the record
     * should be deleted from the zone.
     *
     * @throws IOException The record could not be parsed.
     */
    public void
    delete(Name name, int type, String record) throws IOException {
        newUpdate(Record.fromString(name, type, DClass.NONE, 0, record,
                origin));
    }

    /**
     * Parses a record from the tokenizer, and indicates that the record
     * should be deleted from the zone.
     *
     * @throws IOException The record could not be parsed.
     */
    public void
    delete(Name name, int type, Tokenizer tokenizer) throws IOException {
        newUpdate(Record.fromString(name, type, DClass.NONE, 0, tokenizer,
                origin));
    }

    /**
     * Indicates that the specified record should be deleted from the zone.
     */
    public void
    delete(Record record) {
        newUpdate(record.withDClass(DClass.NONE, 0));
    }

    /**
     * Indicates that the records should be deleted from the zone.
     */
    public void
    delete(Record[] records) {
        for (int i = 0; i < records.length; i++)
            delete(records[i]);
    }

    /**
     * Indicates that all of the records in the rrset should be deleted from the
     * zone.
     */
    public void
    delete(RRset rrset) {
        for (Iterator it = rrset.rrs(); it.hasNext(); )
            delete((Record) it.next());
    }

    /**
     * Parses a record from the string, and indicates that the record
     * should be inserted into the zone replacing any other records with the
     * same name and type.
     *
     * @throws IOException The record could not be parsed.
     */
    public void
    replace(Name name, int type, long ttl, String record) throws IOException {
        delete(name, type);
        add(name, type, ttl, record);
    }

    /**
     * Parses a record from the tokenizer, and indicates that the record
     * should be inserted into the zone replacing any other records with the
     * same name and type.
     *
     * @throws IOException The record could not be parsed.
     */
    public void
    replace(Name name, int type, long ttl, Tokenizer tokenizer) throws IOException {
        delete(name, type);
        add(name, type, ttl, tokenizer);
    }

    /**
     * Indicates that the record should be inserted into the zone replacing any
     * other records with the same name and type.
     */
    public void
    replace(Record record) {
        delete(record.getName(), record.getType());
        add(record);
    }

    /**
     * Indicates that the records should be inserted into the zone replacing any
     * other records with the same name and type as each one.
     */
    public void
    replace(Record[] records) {
        for (int i = 0; i < records.length; i++)
            replace(records[i]);
    }

    /**
     * Indicates that all of the records in the rrset should be inserted into the
     * zone replacing any other records with the same name and type.
     */
    public void
    replace(RRset rrset) {
        delete(rrset.getName(), rrset.getType());
        for (Iterator it = rrset.rrs(); it.hasNext(); )
            add((Record) it.next());
    }

}
