package dns

import (
	"bytes"
	"errors"
	"fmt"
	"strconv"
	"strings"
)

// Parse the $GENERATE statement as used in BIND9 zones.
// See http://www.zytrax.com/books/dns/ch8/generate.html for instance.
// We are called after '$GENERATE '. After which we expect:
// * the range (12-24/2)
// * lhs (ownername)
// * [[ttl][class]]
// * type
// * rhs (rdata)
// But we are lazy here, only the range is parsed *all* occurrences
// of $ after that are interpreted.
// Any error are returned as a string value, the empty string signals
// "no error".
func generate(l lex, c chan lex, t chan *Token, o string) string {
	step := 1
	if i := strings.IndexAny(l.token, "/"); i != -1 {
		if i+1 == len(l.token) {
			return "bad step in $GENERATE range"
		}
		if s, err := strconv.Atoi(l.token[i+1:]); err == nil {
			if s < 0 {
				return "bad step in $GENERATE range"
			}
			step = s
		} else {
			return "bad step in $GENERATE range"
		}
		l.token = l.token[:i]
	}
	sx := strings.SplitN(l.token, "-", 2)
	if len(sx) != 2 {
		return "bad start-stop in $GENERATE range"
	}
	start, err := strconv.Atoi(sx[0])
	if err != nil {
		return "bad start in $GENERATE range"
	}
	end, err := strconv.Atoi(sx[1])
	if err != nil {
		return "bad stop in $GENERATE range"
	}
	if end < 0 || start < 0 || end < start {
		return "bad range in $GENERATE range"
	}

	<-c // _BLANK
	// Create a complete new string, which we then parse again.
	s := ""
BuildRR:
	l = <-c
	if l.value != zNewline && l.value != zEOF {
		s += l.token
		goto BuildRR
	}
	for i := start; i <= end; i += step {
		var (
			escape bool
			dom    bytes.Buffer
			mod    string
			err    error
			offset int
		)

		for j := 0; j < len(s); j++ { // No 'range' because we need to jump around
			switch s[j] {
			case '\\':
				if escape {
					dom.WriteByte('\\')
					escape = false
					continue
				}
				escape = true
			case '$':
				mod = "%d"
				offset = 0
				if escape {
					dom.WriteByte('$')
					escape = false
					continue
				}
				escape = false
				if j+1 >= len(s) { // End of the string
					dom.WriteString(fmt.Sprintf(mod, i+offset))
					continue
				} else {
					if s[j+1] == '$' {
						dom.WriteByte('$')
						j++
						continue
					}
				}
				// Search for { and }
				if s[j+1] == '{' { // Modifier block
					sep := strings.Index(s[j+2:], "}")
					if sep == -1 {
						return "bad modifier in $GENERATE"
					}
					mod, offset, err = modToPrintf(s[j+2 : j+2+sep])
					if err != nil {
						return err.Error()
					}
					j += 2 + sep // Jump to it
				}
				dom.WriteString(fmt.Sprintf(mod, i+offset))
			default:
				if escape { // Pretty useless here
					escape = false
					continue
				}
				dom.WriteByte(s[j])
			}
		}
		// Re-parse the RR and send it on the current channel t
		rx, err := NewRR("$ORIGIN " + o + "\n" + dom.String())
		if err != nil {
			return err.Error()
		}
		t <- &Token{RR: rx}
		// Its more efficient to first built the rrlist and then parse it in
		// one go! But is this a problem?
	}
	return ""
}

// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
func modToPrintf(s string) (string, int, error) {
	xs := strings.Split(s, ",")

	// Modifier is { offset [ ,width [ ,base ] ] } - provide default
	// values for optional width and type, if necessary.
	switch len(xs) {
	case 1:
		xs = append(xs, "0", "d")
	case 2:
		xs = append(xs, "d")
	case 3:
	default:
		return "", 0, errors.New("bad modifier in $GENERATE")
	}

	// xs[0] is offset, xs[1] is width, xs[2] is base
	if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" {
		return "", 0, errors.New("bad base in $GENERATE")
	}
	offset, err := strconv.Atoi(xs[0])
	if err != nil || offset > 255 {
		return "", 0, errors.New("bad offset in $GENERATE")
	}
	width, err := strconv.Atoi(xs[1])
	if err != nil || width > 255 {
		return "", offset, errors.New("bad width in $GENERATE")
	}
	switch {
	case width < 0:
		return "", offset, errors.New("bad width in $GENERATE")
	case width == 0:
		return "%" + xs[1] + xs[2], offset, nil
	}
	return "%0" + xs[1] + xs[2], offset, nil
}
