package req

import (
	"crypto/tls"
	"errors"
	"net"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"time"
)

// create a default client
func newClient(tlsHandshakeTimeout, timeOut time.Duration) *http.Client {
	jar, _ := cookiejar.New(nil)
	transport := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   timeOut,
			KeepAlive: 30 * time.Second,
			DualStack: true,
		}).DialContext,
		MaxIdleConns:    100,
		IdleConnTimeout: 90 * time.Second,
		//TLSHandshakeTimeout:   10 * time.Second,
		TLSHandshakeTimeout:   tlsHandshakeTimeout,
		ExpectContinueTimeout: 1 * time.Second,
	}
	return &http.Client{
		Jar:       jar,
		Transport: transport,
		Timeout:   timeOut,
	}
}

// Client return the default underlying http client
func (r *Req) Client() *http.Client {
	if r.client == nil {
		r.client = newClient(r.tLSHandshakeTimeout, r.timeOut)
	}
	return r.client
}

// Client return the default underlying http client
func Client() *http.Client {
	return std.Client()
}

// SetClient sets the underlying http.Client.
func (r *Req) SetClient(client *http.Client) {
	r.client = client // use default if client == nil
}

// SetClient sets the default http.Client for requests.
func SetClient(client *http.Client) {
	std.SetClient(client)
}

// SetFlags control display format of *Resp
func (r *Req) SetFlags(flags int) {
	r.flag = flags
}

// SetFlags control display format of *Resp
func SetFlags(flags int) {
	std.SetFlags(flags)
}

// Flags return output format for the *Resp
func (r *Req) Flags() int {
	return r.flag
}

// Flags return output format for the *Resp
func Flags() int {
	return std.Flags()
}

func (r *Req) getTransport() *http.Transport {
	trans, _ := r.Client().Transport.(*http.Transport)
	return trans
}

// EnableInsecureTLS allows insecure https
func (r *Req) EnableInsecureTLS(enable bool) {
	trans := r.getTransport()
	if trans == nil {
		return
	}
	if trans.TLSClientConfig == nil {
		trans.TLSClientConfig = &tls.Config{}
	}
	trans.TLSClientConfig.InsecureSkipVerify = enable
}

func EnableInsecureTLS(enable bool) {
	std.EnableInsecureTLS(enable)
}

// EnableCookieenable or disable cookie manager
func (r *Req) EnableCookie(enable bool) {
	if enable {
		jar, _ := cookiejar.New(nil)
		r.Client().Jar = jar
	} else {
		r.Client().Jar = nil
	}
}

// EnableCookieenable or disable cookie manager
func EnableCookie(enable bool) {
	std.EnableCookie(enable)
}

// SetTimeout sets the timeout for every request
func (r *Req) SetTimeout(d time.Duration) {
	r.timeOut = d
	r.Client().Timeout = d
}

// SetTimeout sets the timeout for every request
func SetTimeout(d time.Duration) {
	std.SetTimeout(d)
}

// SetTLSHandshakeTimeout sets the maximum amount of time waiting to wait for a TLS handshake.
func (r *Req) SetTLSHandshakeTimeout(d time.Duration) {
	r.timeOut = d
	r.tLSHandshakeTimeout = d
}

// SetTLSHandshakeTimeout sets the maximum amount of time waiting to wait for a TLS handshake.
func SetTLSHandshakeTimeout(d time.Duration) {
	std.SetTLSHandshakeTimeout(d)
}

// SetProxyUrl set the simple proxy with fixed proxy url
func (r *Req) SetProxyUrl(rawurl string) error {
	trans := r.getTransport()
	if trans == nil {
		return errors.New("req: no transport")
	}
	u, err := url.Parse(rawurl)
	if err != nil {
		return err
	}
	trans.Proxy = http.ProxyURL(u)
	return nil
}

// SetProxyUrl set the simple proxy with fixed proxy url
func SetProxyUrl(rawurl string) error {
	return std.SetProxyUrl(rawurl)
}

// SetProxy sets the proxy for every request
func (r *Req) SetProxy(proxy func(*http.Request) (*url.URL, error)) error {
	trans := r.getTransport()
	if trans == nil {
		return errors.New("req: no transport")
	}
	trans.Proxy = proxy
	return nil
}

// SetProxy sets the proxy for every request
func SetProxy(proxy func(*http.Request) (*url.URL, error)) error {
	return std.SetProxy(proxy)
}

type jsonEncOpts struct {
	indentPrefix string
	indentValue  string
	escapeHTML   bool
}

func (r *Req) getJSONEncOpts() *jsonEncOpts {
	if r.jsonEncOpts == nil {
		r.jsonEncOpts = &jsonEncOpts{escapeHTML: true}
	}
	return r.jsonEncOpts
}

// SetJSONEscapeHTML specifies whether problematic HTML characters
// should be escaped inside JSON quoted strings.
// The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e
// to avoid certain safety problems that can arise when embedding JSON in HTML.
//
// In non-HTML settings where the escaping interferes with the readability
// of the output, SetEscapeHTML(false) disables this behavior.
func (r *Req) SetJSONEscapeHTML(escape bool) {
	opts := r.getJSONEncOpts()
	opts.escapeHTML = escape
}

// SetJSONEscapeHTML specifies whether problematic HTML characters
// should be escaped inside JSON quoted strings.
// The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e
// to avoid certain safety problems that can arise when embedding JSON in HTML.
//
// In non-HTML settings where the escaping interferes with the readability
// of the output, SetEscapeHTML(false) disables this behavior.
func SetJSONEscapeHTML(escape bool) {
	std.SetJSONEscapeHTML(escape)
}

// SetJSONIndent instructs the encoder to format each subsequent encoded
// value as if indented by the package-level function Indent(dst, src, prefix, indent).
// Calling SetIndent("", "") disables indentation.
func (r *Req) SetJSONIndent(prefix, indent string) {
	opts := r.getJSONEncOpts()
	opts.indentPrefix = prefix
	opts.indentValue = indent
}

// SetJSONIndent instructs the encoder to format each subsequent encoded
// value as if indented by the package-level function Indent(dst, src, prefix, indent).
// Calling SetIndent("", "") disables indentation.
func SetJSONIndent(prefix, indent string) {
	std.SetJSONIndent(prefix, indent)
}

type xmlEncOpts struct {
	prefix string
	indent string
}

func (r *Req) getXMLEncOpts() *xmlEncOpts {
	if r.xmlEncOpts == nil {
		r.xmlEncOpts = &xmlEncOpts{}
	}
	return r.xmlEncOpts
}

// SetXMLIndent sets the encoder to generate XML in which each element
// begins on a new indented line that starts with prefix and is followed by
// one or more copies of indent according to the nesting depth.
func (r *Req) SetXMLIndent(prefix, indent string) {
	opts := r.getXMLEncOpts()
	opts.prefix = prefix
	opts.indent = indent
}

// SetXMLIndent sets the encoder to generate XML in which each element
// begins on a new indented line that starts with prefix and is followed by
// one or more copies of indent according to the nesting depth.
func SetXMLIndent(prefix, indent string) {
	std.SetXMLIndent(prefix, indent)
}
