// Package obfs implements simple-obfs of ss
package obfs

import (
	"errors"
	"net"
	"net/url"

	"github.com/nadoo/glider/log"
	"github.com/nadoo/glider/proxy"
)

// Obfs struct.
type Obfs struct {
	dialer proxy.Dialer
	addr   string

	obfsType string
	obfsHost string
	obfsURI  string
	obfsUA   string

	obfsConn func(c net.Conn) (net.Conn, error)
}

func init() {
	proxy.RegisterDialer("simple-obfs", NewObfsDialer)
}

// NewObfs returns a proxy struct.
func NewObfs(s string, d proxy.Dialer) (*Obfs, error) {
	u, err := url.Parse(s)
	if err != nil {
		log.F("parse url err: %s", err)
		return nil, err
	}

	addr := u.Host

	query := u.Query()
	obfsType := query.Get("type")
	if obfsType == "" {
		obfsType = "http"
	}

	obfsHost := query.Get("host")
	if obfsHost == "" {
		return nil, errors.New("[obfs] host cannot be null")
	}

	obfsURI := query.Get("uri")
	if obfsURI == "" {
		obfsURI = "/"
	}

	obfsUA := query.Get("ua")
	if obfsUA == "" {
		obfsUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36"
	}

	p := &Obfs{
		dialer:   d,
		addr:     addr,
		obfsType: obfsType,
		obfsHost: obfsHost,
		obfsURI:  obfsURI,
		obfsUA:   obfsUA,
	}

	switch obfsType {
	case "http":
		httpObfs := NewHTTPObfs(obfsHost, obfsURI, obfsUA)
		p.obfsConn = httpObfs.NewConn
	case "tls":
		tlsObfs := NewTLSObfs(obfsHost)
		p.obfsConn = tlsObfs.NewConn
	default:
		return nil, errors.New("[obfs] unknown obfs type: " + obfsType)
	}

	return p, nil
}

// NewObfsDialer returns a proxy dialer.
func NewObfsDialer(s string, dialer proxy.Dialer) (proxy.Dialer, error) {
	return NewObfs(s, dialer)
}

// Addr returns forwarder's address.
func (s *Obfs) Addr() string {
	if s.addr == "" {
		return s.dialer.Addr()
	}
	return s.addr
}

// Dial connects to the address addr on the network net via the proxy.
func (s *Obfs) Dial(network, addr string) (net.Conn, error) {
	c, err := s.dialer.Dial("tcp", s.addr)
	if err != nil {
		log.F("[obfs] dial to %s error: %s", s.addr, err)
		return nil, err
	}

	return s.obfsConn(c)
}

// DialUDP connects to the given address via the proxy.
func (s *Obfs) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) {
	return nil, nil, proxy.ErrNotSupported
}
