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

package org.xbill.DNS;

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

/**
 * A representation of a $GENERATE statement in a master file.
 *
 * @author Brian Wellington
 */

public class Generator {

    /**
     * The start of the range.
     */
    public long start;

    /**
     * The end of the range.
     */
    public long end;

    /**
     * The step value of the range.
     */
    public long step;

    /**
     * The pattern to use for generating record names.
     */
    public final String namePattern;

    /**
     * The type of the generated records.
     */
    public final int type;

    /**
     * The class of the generated records.
     */
    public final int dclass;

    /**
     * The ttl of the generated records.
     */
    public final long ttl;

    /**
     * The pattern to use for generating record data.
     */
    public final String rdataPattern;

    /**
     * The origin to append to relative names.
     */
    public final Name origin;

    private long current;

    /**
     * Indicates whether generation is supported for this type.
     *
     * @throws InvalidTypeException The type is out of range.
     */
    public static boolean
    supportedType(int type) {
        Type.check(type);
        return (type == Type.PTR || type == Type.CNAME || type == Type.DNAME ||
                type == Type.A || type == Type.AAAA || type == Type.NS);
    }

    /**
     * Creates a specification for generating records, as a $GENERATE
     * statement in a master file.
     *
     * @param start        The start of the range.
     * @param end          The end of the range.
     * @param step         The step value of the range.
     * @param namePattern  The pattern to use for generating record names.
     * @param type         The type of the generated records.  The supported types are
     *                     PTR, CNAME, DNAME, A, AAAA, and NS.
     * @param dclass       The class of the generated records.
     * @param ttl          The ttl of the generated records.
     * @param rdataPattern The pattern to use for generating record data.
     * @param origin       The origin to append to relative names.
     * @throws IllegalArgumentException The range is invalid.
     * @throws IllegalArgumentException The type does not support generation.
     * @throws IllegalArgumentException The dclass is not a valid class.
     */
    public Generator(long start, long end, long step, String namePattern,
                     int type, int dclass, long ttl, String rdataPattern, Name origin) {
        if (start < 0 || end < 0 || start > end || step <= 0)
            throw new IllegalArgumentException
                    ("invalid range specification");
        if (!supportedType(type))
            throw new IllegalArgumentException("unsupported type");
        DClass.check(dclass);

        this.start = start;
        this.end = end;
        this.step = step;
        this.namePattern = namePattern;
        this.type = type;
        this.dclass = dclass;
        this.ttl = ttl;
        this.rdataPattern = rdataPattern;
        this.origin = origin;
        this.current = start;
    }

    private String
    substitute(String spec, long n) throws IOException {
        boolean escaped = false;
        byte[] str = spec.getBytes();
        StringBuffer sb = new StringBuffer();

        for (int i = 0; i < str.length; i++) {
            char c = (char) (str[i] & 0xFF);
            if (escaped) {
                sb.append(c);
                escaped = false;
            } else if (c == '\\') {
                if (i + 1 == str.length)
                    throw new TextParseException
                            ("invalid escape character");
                escaped = true;
            } else if (c == '$') {
                boolean negative = false;
                long offset = 0;
                long width = 0;
                long base = 10;
                boolean wantUpperCase = false;
                if (i + 1 < str.length && str[i + 1] == '$') {
                    // '$$' == literal '$' for backwards
                    // compatibility with old versions of BIND.
                    c = (char) (str[++i] & 0xFF);
                    sb.append(c);
                    continue;
                } else if (i + 1 < str.length && str[i + 1] == '{') {
                    // It's a substitution with modifiers.
                    i++;
                    if (i + 1 < str.length && str[i + 1] == '-') {
                        negative = true;
                        i++;
                    }
                    while (i + 1 < str.length) {
                        c = (char) (str[++i] & 0xFF);
                        if (c == ',' || c == '}')
                            break;
                        if (c < '0' || c > '9')
                            throw new TextParseException(
                                    "invalid offset");
                        c -= '0';
                        offset *= 10;
                        offset += c;
                    }
                    if (negative)
                        offset = -offset;

                    if (c == ',') {
                        while (i + 1 < str.length) {
                            c = (char) (str[++i] & 0xFF);
                            if (c == ',' || c == '}')
                                break;
                            if (c < '0' || c > '9')
                                throw new
                                        TextParseException(
                                        "invalid width");
                            c -= '0';
                            width *= 10;
                            width += c;
                        }
                    }

                    if (c == ',') {
                        if (i + 1 == str.length)
                            throw new TextParseException(
                                    "invalid base");
                        c = (char) (str[++i] & 0xFF);
                        if (c == 'o')
                            base = 8;
                        else if (c == 'x')
                            base = 16;
                        else if (c == 'X') {
                            base = 16;
                            wantUpperCase = true;
                        } else if (c != 'd')
                            throw new TextParseException(
                                    "invalid base");
                    }

                    if (i + 1 == str.length || str[i + 1] != '}')
                        throw new TextParseException
                                ("invalid modifiers");
                    i++;
                }
                long v = n + offset;
                if (v < 0)
                    throw new TextParseException
                            ("invalid offset expansion");
                String number;
                if (base == 8)
                    number = Long.toOctalString(v);
                else if (base == 16)
                    number = Long.toHexString(v);
                else
                    number = Long.toString(v);
                if (wantUpperCase)
                    number = number.toUpperCase();
                if (width != 0 && width > number.length()) {
                    int zeros = (int) width - number.length();
                    while (zeros-- > 0)
                        sb.append('0');
                }
                sb.append(number);
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * Constructs and returns the next record in the expansion.
     *
     * @throws IOException The name or rdata was invalid after substitutions were
     *                     performed.
     */
    public Record
    nextRecord() throws IOException {
        if (current > end)
            return null;
        String namestr = substitute(namePattern, current);
        Name name = Name.fromString(namestr, origin);
        String rdata = substitute(rdataPattern, current);
        current += step;
        return Record.fromString(name, type, dclass, ttl, rdata, origin);
    }

    /**
     * Constructs and returns all records in the expansion.
     *
     * @throws IOException The name or rdata of a record was invalid after
     *                     substitutions were performed.
     */
    public Record[]
    expand() throws IOException {
        List list = new ArrayList();
        for (long i = start; i < end; i += step) {
            String namestr = substitute(namePattern, current);
            Name name = Name.fromString(namestr, origin);
            String rdata = substitute(rdataPattern, current);
            list.add(Record.fromString(name, type, dclass, ttl,
                    rdata, origin));
        }
        return (Record[]) list.toArray(new Record[list.size()]);
    }

    /**
     * Converts the generate specification to a string containing the corresponding
     * $GENERATE statement.
     */
    public String
    toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("$GENERATE ");
        sb.append(start + "-" + end);
        if (step > 1)
            sb.append("/" + step);
        sb.append(" ");
        sb.append(namePattern + " ");
        sb.append(ttl + " ");
        if (dclass != DClass.IN || !Options.check("noPrintIN"))
            sb.append(DClass.string(dclass) + " ");
        sb.append(Type.string(type) + " ");
        sb.append(rdataPattern + " ");
        return sb.toString();
    }

}
