package utils

import (
	"bufio"
	"io"
	"mime"
	"net"
	"net/http"
	"net/url"
)

// ProxyWriter helps to capture response headers and status code
// from the ServeHTTP. It can be safely passed to ServeHTTP handler,
// wrapping the real response writer.
type ProxyWriter struct {
	W    http.ResponseWriter
	Code int
}

func (p *ProxyWriter) StatusCode() int {
	if p.Code == 0 {
		// per contract standard lib will set this to http.StatusOK if not set
		// by user, here we avoid the confusion by mirroring this logic
		return http.StatusOK
	}
	return p.Code
}

func (p *ProxyWriter) Header() http.Header {
	return p.W.Header()
}

func (p *ProxyWriter) Write(buf []byte) (int, error) {
	return p.W.Write(buf)
}

func (p *ProxyWriter) WriteHeader(code int) {
	p.Code = code
	p.W.WriteHeader(code)
}

func (p *ProxyWriter) Flush() {
	if f, ok := p.W.(http.Flusher); ok {
		f.Flush()
	}
}

func (p *ProxyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return p.W.(http.Hijacker).Hijack()
}

func NewBufferWriter(w io.WriteCloser) *BufferWriter {
	return &BufferWriter{
		W: w,
		H: make(http.Header),
	}
}

type BufferWriter struct {
	H    http.Header
	Code int
	W    io.WriteCloser
}

func (b *BufferWriter) Close() error {
	return b.W.Close()
}

func (b *BufferWriter) Header() http.Header {
	return b.H
}

func (b *BufferWriter) Write(buf []byte) (int, error) {
	return b.W.Write(buf)
}

// WriteHeader sets rw.Code.
func (b *BufferWriter) WriteHeader(code int) {
	b.Code = code
}

func (b *BufferWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return b.W.(http.Hijacker).Hijack()
}

type nopWriteCloser struct {
	io.Writer
}

func (*nopWriteCloser) Close() error { return nil }

// NopCloser returns a WriteCloser with a no-op Close method wrapping
// the provided Writer w.
func NopWriteCloser(w io.Writer) io.WriteCloser {
	return &nopWriteCloser{w}
}

// CopyURL provides update safe copy by avoiding shallow copying User field
func CopyURL(i *url.URL) *url.URL {
	out := *i
	if i.User != nil {
		out.User = &(*i.User)
	}
	return &out
}

// CopyHeaders copies http headers from source to destination, it
// does not overide, but adds multiple headers
func CopyHeaders(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

// HasHeaders determines whether any of the header names is present in the http headers
func HasHeaders(names []string, headers http.Header) bool {
	for _, h := range names {
		if headers.Get(h) != "" {
			return true
		}
	}
	return false
}

// RemoveHeaders removes the header with the given names from the headers map
func RemoveHeaders(headers http.Header, names ...string) {
	for _, h := range names {
		headers.Del(h)
	}
}

// Parse the MIME media type value of a header.
func GetHeaderMediaType(headers http.Header, name string) (string, error)  {
	mediatype, _, err := mime.ParseMediaType(headers.Get(name))
	return mediatype, err
}
