package kubernetes

import "github.com/miekg/dns"

// AutoPathWriter implements a ResponseWriter that also does the following:
// * reverts question section of a packet to its original state.
//   This is done to avoid the 'Question section mismatch:' error in client.
// * Defers write to the client if the response code is NXDOMAIN.  This is needed
//   to enable further search path probing if a search was not sucessful.
// * Allow forced write to client regardless of response code.  This is needed to
//   write the packet to the client if the final search in the path fails to
//   produce results.
// * Overwrites response code with AutoPathWriter.Rcode if the response code
//   is NXDOMAIN (NameError).  This is needed to support the AutoPath.OnNXDOMAIN
//   function, which returns a NOERROR to client instead of NXDOMAIN if the final
//   search in the path fails to produce results.

type AutoPathWriter struct {
	dns.ResponseWriter
	original dns.Question
	Rcode    int
	Sent     bool
}

// NewAutoPathWriter returns a pointer to a new AutoPathWriter
func NewAutoPathWriter(w dns.ResponseWriter, r *dns.Msg) *AutoPathWriter {
	return &AutoPathWriter{
		ResponseWriter: w,
		original:       r.Question[0],
		Rcode:          dns.RcodeSuccess,
	}
}

// WriteMsg writes to client, unless response will be NXDOMAIN
func (apw *AutoPathWriter) WriteMsg(res *dns.Msg) error {
	return apw.overrideMsg(res, false)
}

// ForceWriteMsg forces the write to client regardless of response code
func (apw *AutoPathWriter) ForceWriteMsg(res *dns.Msg) error {
	return apw.overrideMsg(res, true)
}

// overrideMsg overrides rcode, reverts question, adds CNAME, and calls the
// underlying ResponseWriter's WriteMsg method unless the write is deferred,
// or force = true.
func (apw *AutoPathWriter) overrideMsg(res *dns.Msg, force bool) error {
	if res.Rcode == dns.RcodeNameError {
		res.Rcode = apw.Rcode
	}
	if res.Rcode != dns.RcodeSuccess && !force {
		return nil
	}
	for _, a := range res.Answer {
		if apw.original.Name == a.Header().Name {
			continue
		}
		res.Answer = append(res.Answer, nil)
		copy(res.Answer[1:], res.Answer)
		res.Answer[0] = newCNAME(apw.original.Name, dns.Fqdn(a.Header().Name), a.Header().Ttl)
	}
	res.Question[0] = apw.original
	apw.Sent = true
	return apw.ResponseWriter.WriteMsg(res)
}

// Write is a wrapper that records the size of the message that gets written.
func (apw *AutoPathWriter) Write(buf []byte) (int, error) {
	n, err := apw.ResponseWriter.Write(buf)
	return n, err
}

// Hijack implements dns.Hijacker. It simply wraps the underlying
// ResponseWriter's Hijack method if there is one, or returns an error.
func (apw *AutoPathWriter) Hijack() {
	apw.ResponseWriter.Hijack()
	return
}
