package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"os"
	"strings"
	"text/template"

	socks5 "github.com/armon/go-socks5"
	"github.com/weaveworks/common/mflag"
	"github.com/weaveworks/common/mflagext"
)

type pacFileParameters struct {
	HostMatch        string
	SocksDestination string
	Aliases          map[string]string
}

const (
	pacfile = `
function FindProxyForURL(url, host) {
	if(shExpMatch(host, "{{.HostMatch}}")) {
		return "SOCKS5 {{.SocksDestination}}";
	}
	{{range $key, $value := .Aliases}}
	if (host == "{{$key}}") {
		return "SOCKS5 {{.SocksDestination}}";
	}
	{{end}}
	return "DIRECT";
}
`
)

func main() {
	var (
		as               []string
		hostMatch        string
		socksDestination string
	)
	mflagext.ListVar(&as, []string{"a", "-alias"}, []string{}, "Specify hostname aliases in the form alias:hostname.  Can be repeated.")
	mflag.StringVar(&hostMatch, []string{"h", "-host-match"}, "*.weave.local", "Specify main host shExpMatch expression in pacfile")
	mflag.StringVar(&socksDestination, []string{"d", "-socks-destination"}, "localhost:8000", "Specify destination host:port in pacfile")
	mflag.Parse()

	var aliases = map[string]string{}
	for _, a := range as {
		parts := strings.SplitN(a, ":", 2)
		if len(parts) != 2 {
			fmt.Printf("'%s' is not a valid alias.\n", a)
			mflag.Usage()
			os.Exit(1)
		}
		aliases[parts[0]] = parts[1]
	}

	go socksProxy(aliases)

	t := template.Must(template.New("pacfile").Parse(pacfile))
	http.HandleFunc("/proxy.pac", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/x-ns-proxy-autoconfig")
		t.Execute(w, pacFileParameters{hostMatch, socksDestination, aliases})
	})

	if err := http.ListenAndServe(":8080", nil); err != nil {
		panic(err)
	}
}

type aliasingResolver struct {
	aliases map[string]string
	socks5.NameResolver
}

func (r aliasingResolver) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) {
	if alias, ok := r.aliases[name]; ok {
		return r.NameResolver.Resolve(ctx, alias)
	}
	return r.NameResolver.Resolve(ctx, name)
}

func socksProxy(aliases map[string]string) {
	conf := &socks5.Config{
		Resolver: aliasingResolver{
			aliases:      aliases,
			NameResolver: socks5.DNSResolver{},
		},
	}
	server, err := socks5.New(conf)
	if err != nil {
		panic(err)
	}
	if err := server.ListenAndServe("tcp", ":8000"); err != nil {
		panic(err)
	}
}
