package httpserver

import (
	"bytes"
	"io"
	"net/http"
	"sync"
	"time"
)

// ResponseRecorder is a type of http.ResponseWriter that captures
// the status code written to it and also the size of the body
// written in the response. A status code does not have
// to be written, however, in which case 200 must be assumed.
// It is best to have the constructor initialize this type
// with that default status code.
//
// Setting the Replacer field allows middlewares to type-assert
// the http.ResponseWriter to ResponseRecorder and set their own
// placeholder values for logging utilities to use.
//
// Beware when accessing the Replacer value; it may be nil!
type ResponseRecorder struct {
	*ResponseWriterWrapper
	Replacer Replacer
	status   int
	size     int
	start    time.Time
}

// NewResponseRecorder makes and returns a new ResponseRecorder.
// Because a status is not set unless WriteHeader is called
// explicitly, this constructor initializes with a status code
// of 200 to cover the default case.
func NewResponseRecorder(w http.ResponseWriter) *ResponseRecorder {
	return &ResponseRecorder{
		ResponseWriterWrapper: &ResponseWriterWrapper{ResponseWriter: w},
		status:                http.StatusOK,
		start:                 time.Now(),
	}
}

// WriteHeader records the status code and calls the
// underlying ResponseWriter's WriteHeader method.
func (r *ResponseRecorder) WriteHeader(status int) {
	r.status = status
	r.ResponseWriterWrapper.WriteHeader(status)
}

// Write is a wrapper that records the size of the body
// that gets written.
func (r *ResponseRecorder) Write(buf []byte) (int, error) {
	n, err := r.ResponseWriterWrapper.Write(buf)
	if err == nil {
		r.size += n
	}
	return n, err
}

// Size returns the size of the recorded response body.
func (r *ResponseRecorder) Size() int {
	return r.size
}

// Status returns the recorded response status code.
func (r *ResponseRecorder) Status() int {
	return r.status
}

// ResponseBuffer is a type that conditionally buffers the
// response in memory. It implements http.ResponseWriter so
// that it can stream the response if it is not buffering.
// Whether it buffers is decided by a func passed into the
// constructor, NewResponseBuffer.
//
// This type implements http.ResponseWriter, so you can pass
// this to the Next() middleware in the chain and record its
// response. However, since the entire response body will be
// buffered in memory, only use this when explicitly configured
// and required for some specific reason. For example, the
// text/template package only parses templates out of []byte
// and not io.Reader, so the templates directive uses this
// type to obtain the entire template text, but only on certain
// requests that match the right Content-Type, etc.
//
// ResponseBuffer also implements io.ReaderFrom for performance
// reasons. The standard lib's http.response type (unexported)
// uses io.Copy to write the body. io.Copy makes an allocation
// if the destination does not have a ReadFrom method (or if
// the source does not have a WriteTo method, but that's
// irrelevant here). Our ReadFrom is smart: if buffering, it
// calls the buffer's ReadFrom, which makes no allocs because
// it is already a buffer! If we're streaming the response
// instead, ReadFrom uses io.CopyBuffer with a pooled buffer
// that is managed within this package.
type ResponseBuffer struct {
	*ResponseWriterWrapper
	Buffer       *bytes.Buffer
	header       http.Header
	status       int
	shouldBuffer func(status int, header http.Header) bool
	stream       bool
	rw           http.ResponseWriter
}

// NewResponseBuffer returns a new ResponseBuffer that will
// use buf to store the full body of the response if shouldBuffer
// returns true. If shouldBuffer returns false, then the response
// body will be streamed directly to rw.
//
// shouldBuffer will be passed the status code and header fields of
// the response. With that information, the function should decide
// whether to buffer the response in memory. For example: the templates
// directive uses this to determine whether the response is the
// right Content-Type (according to user config) for a template.
//
// For performance, the buf you pass in should probably be obtained
// from a sync.Pool in order to reuse allocated space.
func NewResponseBuffer(buf *bytes.Buffer, rw http.ResponseWriter,
	shouldBuffer func(status int, header http.Header) bool) *ResponseBuffer {
	rb := &ResponseBuffer{
		Buffer:       buf,
		header:       make(http.Header),
		status:       http.StatusOK, // default status code
		shouldBuffer: shouldBuffer,
		rw:           rw,
	}
	rb.ResponseWriterWrapper = &ResponseWriterWrapper{ResponseWriter: rw}
	return rb
}

// Header returns the response header map.
func (rb *ResponseBuffer) Header() http.Header {
	return rb.header
}

// WriteHeader calls shouldBuffer to decide whether the
// upcoming body should be buffered, and then writes
// the header to the response.
func (rb *ResponseBuffer) WriteHeader(status int) {
	rb.status = status
	rb.stream = !rb.shouldBuffer(status, rb.header)
	if rb.stream {
		rb.CopyHeader()
		rb.ResponseWriterWrapper.WriteHeader(status)
	}
}

// Write writes buf to rb.Buffer if buffering, otherwise
// to the ResponseWriter directly if streaming.
func (rb *ResponseBuffer) Write(buf []byte) (int, error) {
	if rb.stream {
		return rb.ResponseWriterWrapper.Write(buf)
	}
	return rb.Buffer.Write(buf)
}

// Buffered returns whether rb has decided to buffer the response.
func (rb *ResponseBuffer) Buffered() bool {
	return !rb.stream
}

// CopyHeader copies the buffered header in rb to the ResponseWriter,
// but it does not write the header out.
func (rb *ResponseBuffer) CopyHeader() {
	for field, val := range rb.header {
		rb.ResponseWriterWrapper.Header()[field] = val
	}
}

// ReadFrom avoids allocations when writing to the buffer (if buffering),
// and reduces allocations when writing to the ResponseWriter directly
// (if streaming).
//
// In local testing with the templates directive, req/sec were improved
// from ~8,200 to ~9,600 on templated files by ensuring that this type
// implements io.ReaderFrom.
func (rb *ResponseBuffer) ReadFrom(src io.Reader) (int64, error) {
	if rb.stream {
		// first see if we can avoid any allocations at all
		if wt, ok := src.(io.WriterTo); ok {
			return wt.WriteTo(rb.ResponseWriterWrapper)
		}
		// if not, use a pooled copy buffer to reduce allocs
		// (this improved req/sec from ~25,300 to ~27,000 on
		// static files served directly with the fileserver,
		// but results fluctuated a little on each run).
		// a note of caution:
		// https://go-review.googlesource.com/c/22134#message-ff351762308fe05f6b72a487d6842e3988916486
		buf := respBufPool.Get().([]byte)
		n, err := io.CopyBuffer(rb.ResponseWriterWrapper, src, buf)
		respBufPool.Put(buf) // defer'ing this slowed down benchmarks a smidgin, I think
		return n, err
	}
	return rb.Buffer.ReadFrom(src)
}

// respBufPool is used for io.CopyBuffer when ResponseBuffer
// is configured to stream a response.
var respBufPool = &sync.Pool{
	New: func() interface{} {
		return make([]byte, 32*1024)
	},
}

// Interface guards
var (
	_ HTTPInterfaces = (*ResponseRecorder)(nil)
	_ HTTPInterfaces = (*ResponseBuffer)(nil)
	_ io.ReaderFrom  = (*ResponseBuffer)(nil)
)
